diff --git a/src/database/buffer_pool.cpp b/src/database/buffer_pool.cpp index ecc366c..9c30dfe 100644 --- a/src/database/buffer_pool.cpp +++ b/src/database/buffer_pool.cpp @@ -11,10 +11,6 @@ BufferPool::~BufferPool() { buffer_pool = nullptr; } -int BufferPool::get_page_size() { - return PAGE_SIZE; -} - Page * BufferPool::get_page(TransactionId * tid, PageId * pid, Permissions * perm) { @@ -126,7 +122,7 @@ void BufferPool::InsertTuple(TransactionId * tid, int table_id, Tuple * t) { std::vector page_vector = catalog->get_db_file(table_id)->AddTuple(*tid, *t); for (Page * page: page_vector) { - page->MarkDirty(true, *tid); + page->MarkDirty(true, tid); } } @@ -136,7 +132,7 @@ void BufferPool::DeleteTuple(TransactionId * tid, Tuple * t) { DbFile * db_file = catalog->get_db_file(table_id); - db_file->DeleteTuple(*tid, *t)->MarkDirty(true, *tid); + db_file->DeleteTuple(*tid, *t)->MarkDirty(true, tid); } void BufferPool::FlushAllPages() { diff --git a/src/database/heap_file.cpp b/src/database/heap_file.cpp index c41e2d0..853cd40 100644 --- a/src/database/heap_file.cpp +++ b/src/database/heap_file.cpp @@ -25,7 +25,7 @@ TupleDesc HeapFile::get_tuple_desc() const { } int HeapFile::get_num_pages() { - return (int) ceil(ftell(file) / BufferPool::get_page_size()); + return (int) ceil(ftell(file) / BufferPool::PAGE_SIZE); } Page * HeapFile::ReadPage(PageId * pid) { diff --git a/src/database/heap_page.cpp b/src/database/heap_page.cpp index 1841f1a..f6cb19e 100644 --- a/src/database/heap_page.cpp +++ b/src/database/heap_page.cpp @@ -1,145 +1,237 @@ -#include -#include +#include #include "database.h" #include "heap_page.h" +#include "db_exception.h" +#include "integer_field.h" +#include "no_such_element_exception.h" +#include "string_field.h" namespace buzzdb { -/** Temporarily not available, as std::byte is only available in C++ 17 -HeapPage::HeapPage(HeapPageId id, std::byte data[]) { - this->pid = id; - this->td = &(Database::get_catalog()->get_tuple_desc(id.get_table_id())); - this->numSlots = get_num_tuples(); - //DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data)); - this->read_index = 0; +/** + * Implementation is possibly wrong: + * - Interactions between sstreams and unsigned chars need to be tested + */ +HeapPage::HeapPage(HeapPageId & pid, unsigned char data[]) + : pid(pid), + table_schema(Database::get_catalog()->get_tuple_desc(pid.get_table_id())), + number_of_slots(get_number_of_tuples()), + tuples(0), + header(new unsigned char[get_header_size()]), + old_data(nullptr), + id_of_transaction_that_dirtied_page(nullptr) { + std::stringstream byte_stream; + byte_stream << data; - this->header = new std::byte[get_header_size()]; - for (int i = 0; i < sizeof(header) / sizeof(header[0]); i++) { - header[i] = data[read_index]; - read_index++; + char input_char = 0; + for (int i = 0; i < get_header_size(); i++) { + // TODO: handle IO exception + byte_stream.get(input_char); + header[i] = input_char; } - this->tuples = new Tuple[numSlots]; try { - // allocate and read the actual records of this page - for (int i = 0; i < sizeof(tuples) / sizeof(tuples[0]); i++) { - ReadNextTuple is not declared - tuples[i] = ReadNextTuple(data, i); + for (int i = 0; i < number_of_slots; i++) { + tuples.push_back(ParseStreamForTuple(&byte_stream, i)); } - } catch (std::exception e) { - std::cout << "Exception occurred"; + } catch (NoSuchElementException e) { + // print stack trace } SetBeforeImage(); } -*/ + +HeapPage::~HeapPage() { + for (int slot_index = 0; slot_index < number_of_slots; slot_index++) { + delete tuples.at(slot_index); + } + + delete[] header; + delete[] old_data; + delete id_of_transaction_that_dirtied_page; +} const PageId & HeapPage::get_id() const { return pid; } const TransactionId * HeapPage::get_id_of_last_dirty_transaction() const { - // some code goes here - // return null; + return id_of_transaction_that_dirtied_page; } -void HeapPage::MarkDirty(bool dirty, TransactionId & tid) { +void HeapPage::MarkDirty(bool dirty, TransactionId * tid) { + id_of_transaction_that_dirtied_page = dirty ? tid : nullptr; } +// uses dynamic memory allocatiom: BEWARE +// update documentation to reflect this +// ensure that memory is released after use Page * HeapPage::GetBeforeImage() { try { - /* - std::byte * old_data_ref = nullptr; - old_data_ref = old_data; - return new HeapPage(pid, old_data_ref); - */ - return new HeapPage(); - } catch (std::exception e) { - /* - System.exit(1); - */ + return new HeapPage(pid, old_data); + } catch (std::exception io_exception) { + // implement properly } + return nullptr; } void HeapPage::SetBeforeImage() { + delete[] old_data; + CreatePageDataRepresentation(old_data); } -int HeapPage::get_num_tuples() { - double pagesize = (double) Database::get_buffer_pool()->get_page_size() * 8; - double tuplesize = (double) (td->get_size() * 8 + 1); - double res = pagesize / tuplesize; - return (int) floor(res); +int HeapPage::get_number_of_tuples() { + // bitwise shift left 3 bits to convert from bytes to bits + int page_size_in_bits = Database::get_buffer_pool()->PAGE_SIZE << 3; + int tuple_size_in_bits = table_schema.get_size() << 3; + int padding_bit = 1; + + return page_size_in_bits / (tuple_size_in_bits + padding_bit); } int HeapPage::get_header_size() { - double res = (double) numSlots / (double) 8; - return (int) ceil(res); + return (number_of_slots + 7) >> 3; } -/* -Tuple HeapPage::ReadNextTuple(byte[] data, int slotId) { - if (!isSlotUsed(slotId)) { - for (int i = 0; i < td.getSize(); i++) { +void HeapPage::CreatePageDataRepresentation(unsigned char * rep) { + std::stringstream byte_stream; + + // write header to stream + // might not be correct to read whole array into stream + // how about the ending characters in an array? + try { + // correctness needs to be checked + byte_stream << *header; + } catch (std::exception io_exception) { + // change catch type + // print stack trace + } + + // write tuples to stream + for (int slot_index = 0; slot_index < number_of_slots; slot_index++) { + // slot has no tuple in it; add empty tuple to stream + if (!IsSlotUsed(slot_index)) { + for (int i = 0; i < table_schema.get_size(); i++) { + try { + byte_stream << '0'; + } catch (std::exception io_exception) { + // change catch type + // print stack trace + } + } + continue; + } + + // slot has a tuple; add it to stream + Tuple * tuple_at_slot_index = tuples.at(slot_index); + for (int field_index = 0; + field_index < table_schema.get_number_fields(); + field_index++) { + Field * field = tuple_at_slot_index->get_field(field_index); try { - readIndex++; - } catch (IOException e) { - throw new NoSuchElementException("error reading empty tuple"); + field->Serialize(&byte_stream); + } catch (std::exception io_exception) { + // change catch type + // print stack trace } } - return null; } - // read fields in the tuple - tuple t = new tuple(td); - RecordId rid = new RecordId(pid, slotId); - t.setRecordId(rid); + // add padding to stream: fill the remaining space with zeroes + // the number of elements in the byte array should = BufferPool::PAGE_SIZE. + int padding_length = BufferPool::PAGE_SIZE + - (get_header_size() + + table_schema.get_size() * number_of_slots); + unsigned char padding[padding_length] = {0}; + try { - for (int j = 0; j < td.numFields(); j++) { - field f = td.getFieldType(j).parse(dis); - t.setField(j, f); - } - } catch (Exception e) { - throw new NoSuchElementException("parsing error!"); + byte_stream << padding; + } catch (std::exception io_exception) { + // change catch type + // print stack trace } - return t; -} -void HeapPage::GetPageData(std::byte rep[]) { -} + /* flush byte stream: necessary? + try { + // flush byte stream + } catch (std::exception io_exception) { + // change catch type + // print stack trace + } + */ + + // extracting the byte_stream contents into an unsigned char array + std::string bs_string_rep = byte_stream.str(); + char * bs_char_array_rep = new char[BufferPool::PAGE_SIZE]; + strncpy(bs_char_array_rep, bs_string_rep.c_str(), BufferPool::PAGE_SIZE); -void HeapPage::CreateEmptyPageData(std::byte rep[]) { - int len = BufferPool.getPageSize(); - return new byte[len]; + rep = (unsigned char *) bs_char_array_rep; } -*/ -void HeapPage::DeleteTuple(Tuple t) { +void HeapPage::CreateEmptyPageDataRepresentation(unsigned char * rep) { + rep = new unsigned char[BufferPool::PAGE_SIZE]; } -void HeapPage::InsertTuple(Tuple t) { +void HeapPage::DeleteTuple(Tuple * t) { + if (pid == t->get_record_id()->get_page_id()) { + int tuple_number = t->get_record_id()->get_tuple_number(); + + if (tuple_number >= 0 + && tuple_number < number_of_slots + && IsSlotUsed(tuple_number)) { + SetSlot(tuple_number, false); + // update tuple's record id + delete t->get_record_id(); + t->set_record_id(nullptr); + } + } + + throw DbException("Tuple not found on page"); } -void HeapPage::AddTuple(Tuple t) { +void HeapPage::InsertTuple(Tuple * t) { + if (table_schema != t->get_tuple_desc()) { + throw DbException("Schema mismatch: Table and tuple"); + } + if (t->get_record_id() != nullptr) { + if (pid == t->get_record_id()->get_page_id()) { + throw DbException("Tuple already resides on this page."); + } + throw DbException("Tuple already resides on another page."); + } + + for (int slot_index = 0; slot_index < number_of_slots; slot_index++) { + if (!IsSlotUsed(slot_index)) { + tuples.at(slot_index) = t; + t->set_record_id(new RecordId(pid, slot_index)); + SetSlot(slot_index, true); + return; + } + } + + throw DbException("This page is full."); } int HeapPage::GetNumEmptySlots() { int count = 0; - for (int i = 0; i < sizeof(tuples) / sizeof(tuples[0]); ++i) { - if (!IsSlotUsed(i)) + for (int slot_index = 0; slot_index < number_of_slots; slot_index++) { + if (!IsSlotUsed(slot_index)) count++; } return count; } -bool HeapPage::IsSlotUsed(int i) { - int x = i / 8; - int y = i % 8; - - /* Error here - return ((header[x] >> y) & 1) == 1; - */ +bool HeapPage::IsSlotUsed(int index) { + // why? + return (header[index >> 3] & (1 << (index & 7))) != 0; } -void HeapPage::SetSlot(int i, bool value) { +void HeapPage::SetSlot(int index, bool updated_status_of_slot) { + // why? + if (updated_status_of_slot) { + header[index >> 3] |= (1 << (index & 7)); + } else { + header[index >> 3] &= ~(1 << (index & 7)); + } } /* Not implemented @@ -152,4 +244,57 @@ Iterator HeapPage::iterator() { return arr.iterator(); } */ + +// uses dynamic memory allocatiom: BEWARE +// update documentation to reflect this +// ensure that memory is released after use +Tuple * HeapPage::ParseStreamForTuple(std::stringstream * byte_stream_pointer, + int slot_index) { + // if the slot is not set to be used, move internal stream pointer forward + // to next tuple, and return nullptr + char input_char = 0; + if (!IsSlotUsed(slot_index)) { + for (int i = 0; i < table_schema.get_size(); i++) { + try { + byte_stream_pointer->get(input_char); + } catch (std::exception io_exception) { + // change catch type + throw NoSuchElementException("Error parsing empty tuple."); + } + } + return nullptr; + } + + // otherwise, parse the tuple. + Tuple * next_tuple = new Tuple(table_schema); + RecordId * rid = new RecordId(pid, slot_index); + next_tuple->set_record_id(rid); + + for (int field_index = 0; + field_index < table_schema.get_number_fields(); + field_index++) { + Field::Type field_type = table_schema.get_field_type(field_index); + Field * parsed_field; + try { + switch (field_type) { + case Field::Type::INTEGER: + parsed_field = + IntegerField::ParseStreamForField(byte_stream_pointer); + break; + case Field::Type::STRING: + parsed_field = + StringField::ParseStreamForField(byte_stream_pointer); + break; + default: + throw std::runtime_error("Field is of invalid type."); + } + } catch (std::exception parse_exception) { + // change catch type + throw NoSuchElementException("Error parsing tuple."); + } + next_tuple->set_field(field_index, parsed_field); + } + + return next_tuple; +} } diff --git a/src/database/integer_field.cpp b/src/database/integer_field.cpp index c980a59..f4019cc 100644 --- a/src/database/integer_field.cpp +++ b/src/database/integer_field.cpp @@ -17,6 +17,15 @@ Field::Type IntegerField::get_type() const { return Type::INTEGER; } +Field * IntegerField::ParseStreamForField( + std::stringstream * byte_stream_pointer) { + // to be implemented +} + +void IntegerField::Serialize(std::stringstream * byte_stream_pointer) { + // to be implemented +} + /* bool IntegerField::Compare(Predicate::OpType op_type, Field * operand) { IntegerField * operand_value_pointer = static_cast(operand); diff --git a/src/database/string_field.cpp b/src/database/string_field.cpp index cb8a0e1..99ec5c7 100644 --- a/src/database/string_field.cpp +++ b/src/database/string_field.cpp @@ -17,6 +17,15 @@ Field::Type StringField::get_type() const { return Type::STRING; } +Field * StringField::ParseStreamForField( + std::stringstream * byte_stream_pointer) { + // to be implemented +} + +void StringField::Serialize(std::stringstream * byte_stream_pointer) { + // to be implemented +} + /* bool StringField::Compare(Predicate::OpType op_type, Field * operand) { StringField * operand_value_pointer = static_cast(operand); diff --git a/src/include/buffer_pool.h b/src/include/buffer_pool.h index 64c68b9..e5425aa 100644 --- a/src/include/buffer_pool.h +++ b/src/include/buffer_pool.h @@ -28,14 +28,17 @@ class BufferPool { */ static const int DEFAULT_PAGES = 50; + /** + * Bytes per page, including header. + */ + static const int PAGE_SIZE = 4096; + std::vector PageList; BufferPool(int num_pages, Catalog * catalog); ~BufferPool(); - static int get_page_size(); - /** * Retrieve the specified page with the associated permissions. * Will acquire a lock and may block if that lock is held by another @@ -136,10 +139,6 @@ class BufferPool { void FlushPages(TransactionId * tid); private: - /** - * Bytes per page, including header. - */ - static const int PAGE_SIZE = 4096; std::vector * buffer_pool; diff --git a/src/include/field.h b/src/include/field.h index 513bf23..354670a 100644 --- a/src/include/field.h +++ b/src/include/field.h @@ -1,8 +1,17 @@ #pragma once +#include + namespace buzzdb { /** * The Field abstract class is an interface for a field in a tuple. + * - Every class that implements this interface must implement the following + * method: + * static Field * ParseStreamForField(std::stringstream byte_stream_pointer) + * - The above constraint cannot be enforced by the specifications of this + * interface as static methods cannot be virtual. + * - If this method is not implemented in a particular Field subclass, it will + * not be possible to parse the particular field type from the byte stream. */ class Field { public: @@ -29,9 +38,7 @@ class Field { /** * Write the bytes representing the field to the specified Stream. */ - /* - virtual void serialize(DataOutputStream dos) = 0; - */ + virtual void Serialize(std::stringstream * byte_stream_pointer) = 0; /** * Compares the value of the Field to the value of operand. diff --git a/src/include/heap_page.h b/src/include/heap_page.h index f4e202e..d5ae007 100644 --- a/src/include/heap_page.h +++ b/src/include/heap_page.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include "heap_page_id.h" #include "page.h" #include "transaction_id.h" @@ -9,21 +11,25 @@ namespace buzzdb { /** * The HeapPage class represents a page in a table implemented as a HeapFile. * - This class implements the Page interface. - * - * Some functions are not available because they use std::byte, which is only - * available in C++ 17. Currently working on a different way to represent pages */ class HeapPage : public Page { public: /** * Default constructor. */ - HeapPage(); + HeapPage() = delete; + + /** + * Destructor + */ + ~HeapPage(); /** * Constructor. - HeapPage(HeapPageId id, std::byte data[]); + * - data is a representation of a page's data and will be parsed by the + * constructor to build the new HeapPage. */ + HeapPage(HeapPageId & id, unsigned char data[]); /** * Returns the id of the page. @@ -39,11 +45,13 @@ class HeapPage : public Page { /** * Sets the dirty state of the page as dirtied by a particular transaction. */ - void MarkDirty(bool dirty, TransactionId & tid) override; + void MarkDirty(bool dirty, TransactionId * tid) override; /** * Returns a representation of the Page before any modifications were made to - * it. Used by recovery. + * it. + * + * Used by recovery. */ Page * GetBeforeImage() override; @@ -56,32 +64,66 @@ class HeapPage : public Page { /** * Returns the number of tuples in the heap page. */ - int get_num_tuples(); + int get_number_of_tuples(); /** * Returns the size of the heap page's header. */ int get_header_size(); - /* Not implemented - Tuple ReadNextTuple(DataInputStream dis, int slotId)(); - */ - - // void GetPageData(std::byte rep[]); - - // static void CreateEmptyPageData(std::byte rep[]); + /** + * Generates an unsigned char array representing the heap page's contents. + * + * Used to serialize the page to disk. + * When the array created is parsed into the HeapPage constructor, a new + * heap page with identical contents should be created. + */ + void CreatePageDataRepresentation(unsigned char * rep); - void DeleteTuple(Tuple t); + /** + * Generated an unsigned char array representing an empty heap page's + * contents. + */ + static void CreateEmptyPageDataRepresentation(unsigned char * rep); - void InsertTuple(Tuple t); + /** + * Deletes the specified tuple from the heap page. + * + * The tuple's record id will be updated accordingly. + * + * Throws: + * - DbException: If the tuple is not on the heap page, + * or if the tuple's slot is already empty. + */ + void DeleteTuple(Tuple * t); - void AddTuple(Tuple t); + /** + * Adds the specified tuple to the heap page. + * + * The tuple's record id will be updated accordingly. + * + * Throws: + * - DbException: If the tuple's schema does not match the table's scheme, + * or if the tuple already resides on a page, + * or if the heap page has no empty slots. + */ + void InsertTuple(Tuple * t); + /** + * Returns the number of empty slots on the heap page. + */ int GetNumEmptySlots(); - bool IsSlotUsed(int i); + /** + * Returns true if the slot given by the index is filled, + * and false otherwise. + */ + bool IsSlotUsed(int index); - void SetSlot(int i, bool value); + /** + * Fills or clears a slot on the heap page. + */ + void SetSlot(int index, bool updated_status_of_slot); /* Not implemented Iterator iterator(); @@ -89,13 +131,23 @@ class HeapPage : public Page { private: HeapPageId pid; - TupleDesc * td; - // std::byte * header; - Tuple * tuples; - int numSlots; - // std::byte * old_data; - - // byte oldDataLock = new byte(0); - int read_index; + TupleDesc table_schema; + int number_of_slots; + std::vector tuples; + unsigned char * header; + unsigned char * old_data; + TransactionId * id_of_transaction_that_dirtied_page; + + /** + * If the slot given by slot_index is set to be used, the next tuple is + * parsed from the given byte stream and returned. Otherwise, the internal + * stream pointer is moved forward to the next tuple and a nullptr is + * returned. + * + * Throws: + * - NoSuchElementException: If there is an error while parsing the tuple. + */ + Tuple * ParseStreamForTuple(std::stringstream * byte_stream_pointer, + int slot_index); }; } diff --git a/src/include/integer_field.h b/src/include/integer_field.h index d0eb6ec..e1d3b3a 100644 --- a/src/include/integer_field.h +++ b/src/include/integer_field.h @@ -34,12 +34,15 @@ class IntegerField : public Field { */ Type get_type() const override; + /** + * Creates an IntegerField by parsing the stream and returns a pointer to it. + */ + static Field * ParseStreamForField(std::stringstream * byte_stream_pointer); + /** * Write the bytes representing the IntegerField to the specified Stream. */ - /* To be implemented - void serialize(DataOutputStream dos) override; - */ + void Serialize(std::stringstream * byte_stream_pointer) override; /** * Compares the value of the IntegerField to the value of operand. diff --git a/src/include/page.h b/src/include/page.h index e3b44e8..b0a61ba 100644 --- a/src/include/page.h +++ b/src/include/page.h @@ -6,11 +6,12 @@ namespace buzzdb { /** * The Page interface class is an interface for page representations. + * - This interface is largely utilized by the BufferPool. * - A page contains tuples, which contain fields. - * - A page is contained in a table, which is typically implemented as a DbFile. - * - * Pages may be "dirty", meaning that they have been modified since they - * were last written out to disk. + * - A page is contained in a table, which is typically implemented as a + * DbFile. + * - A page may be "dirty", meaning that it has been modified since it was last + * written out to disk. */ class Page { public: @@ -35,7 +36,7 @@ class Page { /** * Sets the dirty state of the page as dirtied by a particular transaction. */ - virtual void MarkDirty(bool dirty, TransactionId & tid) = 0; + virtual void MarkDirty(bool dirty, TransactionId * tid) = 0; /* Need to use another input parameter type besides byte virtual void GetPageData(std::byte content_rep[]) = 0; diff --git a/src/include/string_field.h b/src/include/string_field.h index a26f4dc..1472547 100644 --- a/src/include/string_field.h +++ b/src/include/string_field.h @@ -35,12 +35,15 @@ class StringField : public Field { */ Type get_type() const override; + /** + * Creates an StringField by parsing the stream and returns a pointer to it. + */ + static Field * ParseStreamForField(std::stringstream * byte_stream_pointer); + /** * Write the bytes representing the StringField to the specified Stream. */ - /* To be implemented - void serialize(DataOutputStream dos) override; - */ + void Serialize(std::stringstream * byte_stream_pointer) override; /** * Compares the value of the StringField to the value of operand.