From f116a868be32c748290853ce6d9e0050662fbf79 Mon Sep 17 00:00:00 2001 From: "Leandro A. F. Pereira" Date: Thu, 12 Oct 2017 20:49:05 -0700 Subject: [PATCH 01/58] Pointer arithmetic on void pointer is undefined behavior It's undefined behavior since pointer arithmetic needs to know the sizeof the pointed type, and there's no `void` type that sizeof() will work on. By casting the pointer to a char*, the compiler can safely use sizeof(char) in order to perform the calculation. --- _parts/part8.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part8.md b/_parts/part8.md index d811ed3..759d882 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -93,11 +93,11 @@ The code to access keys, values and metadata all involve pointer arithmetic usin ```diff +uint32_t* leaf_node_num_cells(void* node) { -+ return node + LEAF_NODE_NUM_CELLS_OFFSET; ++ return (char *)node + LEAF_NODE_NUM_CELLS_OFFSET; +} + +void* leaf_node_cell(void* node, uint32_t cell_num) { -+ return node + LEAF_NODE_HEADER_SIZE + cell_num * LEAF_NODE_CELL_SIZE; ++ return (char *)node + LEAF_NODE_HEADER_SIZE + cell_num * LEAF_NODE_CELL_SIZE; +} + +uint32_t* leaf_node_key(void* node, uint32_t cell_num) { From a6d525fbfa6bf0c89742c4bc50d8d2d216c5b287 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Mon, 9 Oct 2017 16:51:34 -0700 Subject: [PATCH 02/58] Split leaf node and create new root. --- db.c | 249 ++++++++++++++++++++++++++++++++++++++++------ spec/main_spec.rb | 41 +++++++- 2 files changed, 254 insertions(+), 36 deletions(-) diff --git a/db.c b/db.c index d1e07de..02c82c1 100644 --- a/db.c +++ b/db.c @@ -107,6 +107,26 @@ const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE; const uint8_t COMMON_NODE_HEADER_SIZE = NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE; +/* + * Internal Node Header Layout + */ +const uint32_t INTERNAL_NODE_NUM_KEYS_SIZE = sizeof(uint32_t); +const uint32_t INTERNAL_NODE_NUM_KEYS_OFFSET = COMMON_NODE_HEADER_SIZE; +const uint32_t INTERNAL_NODE_RIGHT_CHILD_SIZE = sizeof(uint32_t); +const uint32_t INTERNAL_NODE_RIGHT_CHILD_OFFSET = + INTERNAL_NODE_NUM_KEYS_OFFSET + INTERNAL_NODE_NUM_KEYS_SIZE; +const uint32_t INTERNAL_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE + + INTERNAL_NODE_NUM_KEYS_SIZE + + INTERNAL_NODE_RIGHT_CHILD_SIZE; + +/* + * Internal Node Body Layout + */ +const uint32_t INTERNAL_NODE_KEY_SIZE = sizeof(uint32_t); +const uint32_t INTERNAL_NODE_CHILD_SIZE = sizeof(uint32_t); +const uint32_t INTERNAL_NODE_CELL_SIZE = + INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE; + /* * Leaf Node Header Layout */ @@ -127,6 +147,9 @@ const uint32_t LEAF_NODE_CELL_SIZE = LEAF_NODE_KEY_SIZE + LEAF_NODE_VALUE_SIZE; const uint32_t LEAF_NODE_SPACE_FOR_CELLS = PAGE_SIZE - LEAF_NODE_HEADER_SIZE; const uint32_t LEAF_NODE_MAX_CELLS = LEAF_NODE_SPACE_FOR_CELLS / LEAF_NODE_CELL_SIZE; +const uint32_t LEAF_NODE_RIGHT_SPLIT_COUNT = (LEAF_NODE_MAX_CELLS + 1) / 2; +const uint32_t LEAF_NODE_LEFT_SPLIT_COUNT = + (LEAF_NODE_MAX_CELLS + 1) - LEAF_NODE_RIGHT_SPLIT_COUNT; NodeType get_node_type(void* node) { uint8_t value = *((uint8_t*)(node + NODE_TYPE_OFFSET)); @@ -138,6 +161,44 @@ void set_node_type(void* node, NodeType type) { *((uint8_t*)(node + NODE_TYPE_OFFSET)) = value; } +bool is_node_root(void* node) { + uint8_t value = *((uint8_t*)(node + IS_ROOT_OFFSET)); + return (bool)value; +} + +void set_node_root(void* node, bool is_root) { + uint8_t value = is_root; + *((uint8_t*)(node + IS_ROOT_OFFSET)) = value; +} + +uint32_t* internal_node_num_keys(void* node) { + return node + INTERNAL_NODE_NUM_KEYS_OFFSET; +} + +uint32_t* internal_node_right_child(void* node) { + return node + INTERNAL_NODE_RIGHT_CHILD_OFFSET; +} + +uint32_t* internal_node_cell(void* node, uint32_t cell_num) { + return node + INTERNAL_NODE_HEADER_SIZE + cell_num * INTERNAL_NODE_CELL_SIZE; +} + +uint32_t* internal_node_child(void* node, uint32_t child_num) { + uint32_t num_keys = *internal_node_num_keys(node); + if (child_num > num_keys) { + printf("Tried to access child_num %d > num_keys %d\n", child_num, num_keys); + exit(EXIT_FAILURE); + } else if (child_num == num_keys) { + return internal_node_right_child(node); + } else { + return internal_node_cell(node, child_num); + } +} + +uint32_t* internal_node_key(void* node, uint32_t key_num) { + return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; +} + uint32_t* leaf_node_num_cells(void* node) { return node + LEAF_NODE_NUM_CELLS_OFFSET; } @@ -154,6 +215,15 @@ void* leaf_node_value(void* node, uint32_t cell_num) { return leaf_node_cell(node, cell_num) + LEAF_NODE_KEY_SIZE; } +uint32_t get_node_max_key(void* node) { + switch (get_node_type(node)) { + case NODE_INTERNAL: + return *internal_node_key(node, *internal_node_num_keys(node) - 1); + case NODE_LEAF: + return *leaf_node_key(node, *leaf_node_num_cells(node) - 1); + } +} + void print_constants() { printf("ROW_SIZE: %d\n", ROW_SIZE); printf("COMMON_NODE_HEADER_SIZE: %d\n", COMMON_NODE_HEADER_SIZE); @@ -163,32 +233,6 @@ void print_constants() { printf("LEAF_NODE_MAX_CELLS: %d\n", LEAF_NODE_MAX_CELLS); } -void print_leaf_node(void* node) { - uint32_t num_cells = *leaf_node_num_cells(node); - printf("leaf (size %d)\n", num_cells); - for (uint32_t i = 0; i < num_cells; i++) { - uint32_t key = *leaf_node_key(node, i); - printf(" - %d : %d\n", i, key); - } -} - -void serialize_row(Row* source, void* destination) { - memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); - memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); - memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); -} - -void deserialize_row(void* source, Row* destination) { - memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); - memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); - memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); -} - -void initialize_leaf_node(void* node) { - set_node_type(node, NODE_LEAF); - *leaf_node_num_cells(node) = 0; -} - void* get_page(Pager* pager, uint32_t page_num) { if (page_num > TABLE_MAX_PAGES) { printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, @@ -225,6 +269,67 @@ void* get_page(Pager* pager, uint32_t page_num) { return pager->pages[page_num]; } +void indent(uint32_t level) { + for (uint32_t i = 0; i < level; i++) { + printf(" "); + } +} + +void print_tree(Pager* pager, uint32_t page_num, uint32_t indentation_level) { + void* node = get_page(pager, page_num); + uint32_t num_keys, child; + + switch (get_node_type(node)) { + case (NODE_LEAF): + num_keys = *leaf_node_num_cells(node); + indent(indentation_level); + printf("- leaf (size %d)\n", num_keys); + for (uint32_t i = 0; i < num_keys; i++) { + indent(indentation_level + 1); + printf("- %d\n", *leaf_node_key(node, i)); + } + break; + case (NODE_INTERNAL): + num_keys = *internal_node_num_keys(node); + indent(indentation_level); + printf("- internal (size %d)\n", num_keys); + for (uint32_t i = 0; i < num_keys; i++) { + child = *internal_node_child(node, i); + print_tree(pager, child, indentation_level + 1); + + indent(indentation_level + 1); + printf("- key %d\n", *internal_node_key(node, i)); + } + child = *internal_node_right_child(node); + print_tree(pager, child, indentation_level + 1); + break; + } +} + +void serialize_row(Row* source, void* destination) { + memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); + memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); + memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); +} + +void deserialize_row(void* source, Row* destination) { + memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); + memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); + memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); +} + +void initialize_leaf_node(void* node) { + set_node_type(node, NODE_LEAF); + set_node_root(node, false); + *leaf_node_num_cells(node) = 0; +} + +void initialize_internal_node(void* node) { + set_node_type(node, NODE_INTERNAL); + set_node_root(node, false); + *internal_node_num_keys(node) = 0; +} + Cursor* table_start(Table* table) { Cursor* cursor = malloc(sizeof(Cursor)); cursor->table = table; @@ -342,6 +447,7 @@ Table* db_open(const char* filename) { // New database file. Initialize page 0 as leaf node. void* root_node = get_page(pager, 0); initialize_leaf_node(root_node); + set_node_root(root_node, true); } return table; @@ -427,7 +533,7 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table) { exit(EXIT_SUCCESS); } else if (strcmp(input_buffer->buffer, ".btree") == 0) { printf("Tree:\n"); - print_leaf_node(get_page(table->pager, 0)); + print_tree(table->pager, 0, 0); return META_COMMAND_SUCCESS; } else if (strcmp(input_buffer->buffer, ".constants") == 0) { printf("Constants:\n"); @@ -481,14 +587,96 @@ PrepareResult prepare_statement(InputBuffer* input_buffer, return PREPARE_UNRECOGNIZED_STATEMENT; } +/* +Until we start recycling free pages, new pages will always +go onto the end of the database file +*/ +uint32_t get_unused_page_num(Pager* pager) { return pager->num_pages; } + +void create_new_root(Table* table, uint32_t right_child_page_num) { + /* + Handle splitting the root. + Old root copied to new page, becomes left child. + Address of right child passed in. + Re-initialize root page to contain the new root node. + New root node points to two children. + */ + + void* root = get_page(table->pager, table->root_page_num); + void* right_child = get_page(table->pager, right_child_page_num); + uint32_t left_child_page_num = get_unused_page_num(table->pager); + void* left_child = get_page(table->pager, left_child_page_num); + + /* Left child has data copied from old root */ + memcpy(left_child, root, PAGE_SIZE); + set_node_root(left_child, false); + + /* Root node is a new internal node with one key and two children */ + initialize_internal_node(root); + set_node_root(root, true); + *internal_node_num_keys(root) = 1; + *internal_node_child(root, 0) = left_child_page_num; + uint32_t left_child_max_key = get_node_max_key(left_child); + *internal_node_key(root, 0) = left_child_max_key; + *internal_node_right_child(root) = right_child_page_num; +} + +void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { + /* + Create a new node and move half the cells over. + Insert the new value in one of the two nodes. + Update parent or create a new parent. + */ + + void* old_node = get_page(cursor->table->pager, cursor->page_num); + uint32_t new_page_num = get_unused_page_num(cursor->table->pager); + void* new_node = get_page(cursor->table->pager, new_page_num); + initialize_leaf_node(new_node); + + /* + All existing keys plus new key should should be divided + evenly between old (left) and new (right) nodes. + Starting from the right, move each key to correct position. + */ + for (int32_t i = LEAF_NODE_MAX_CELLS; i >= 0; i--) { + void* destination_node; + if (i >= LEAF_NODE_LEFT_SPLIT_COUNT) { + destination_node = new_node; + } else { + destination_node = old_node; + } + uint32_t index_within_node = i % LEAF_NODE_LEFT_SPLIT_COUNT; + void* destination = leaf_node_cell(destination_node, index_within_node); + + if (i == cursor->cell_num) { + serialize_row(value, destination); + } else if (i > cursor->cell_num) { + memcpy(destination, leaf_node_cell(old_node, i - 1), LEAF_NODE_CELL_SIZE); + } else { + memcpy(destination, leaf_node_cell(old_node, i), LEAF_NODE_CELL_SIZE); + } + } + + /* Update cell count on both leaf nodes */ + *(leaf_node_num_cells(old_node)) = LEAF_NODE_LEFT_SPLIT_COUNT; + *(leaf_node_num_cells(new_node)) = LEAF_NODE_RIGHT_SPLIT_COUNT; + + if (is_node_root(old_node)) { + return create_new_root(cursor->table, new_page_num); + } else { + printf("Need to implement updating parent after split\n"); + exit(EXIT_FAILURE); + } +} + void leaf_node_insert(Cursor* cursor, uint32_t key, Row* value) { void* node = get_page(cursor->table->pager, cursor->page_num); uint32_t num_cells = *leaf_node_num_cells(node); if (num_cells >= LEAF_NODE_MAX_CELLS) { // Node full - printf("Need to implement splitting a leaf node.\n"); - exit(EXIT_FAILURE); + leaf_node_split_and_insert(cursor, key, value); + return; } if (cursor->cell_num < num_cells) { @@ -507,9 +695,6 @@ void leaf_node_insert(Cursor* cursor, uint32_t key, Row* value) { ExecuteResult execute_insert(Statement* statement, Table* table) { void* node = get_page(table->pager, table->root_page_num); uint32_t num_cells = (*leaf_node_num_cells(node)); - if (num_cells >= LEAF_NODE_MAX_CELLS) { - return EXECUTE_TABLE_FULL; - } Row* row_to_insert = &(statement->row_to_insert); uint32_t key_to_insert = row_to_insert->id; diff --git a/spec/main_spec.rb b/spec/main_spec.rb index 4c88b0b..30eee90 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -139,14 +139,47 @@ def run_script(commands) "db > Executed.", "db > Executed.", "db > Tree:", - "leaf (size 3)", - " - 0 : 1", - " - 1 : 2", - " - 2 : 3", + "- leaf (size 3)", + " - 1", + " - 2", + " - 3", "db > " ]) end + it 'allows printing out the structure of a 3-leaf-node btree' do + script = (1..14).map do |i| + "insert #{i} user#{i} person#{i}@example.com" + end + script << ".btree" + script << "insert 15 user15 person15@example.com" + script << ".exit" + result = run_script(script) + + expect(result[14...(result.length)]).to eq([ + "db > Tree:", + "- internal (size 1)", + " - leaf (size 7)", + " - 1", + " - 2", + " - 3", + " - 4", + " - 5", + " - 6", + " - 7", + " - key 7", + " - leaf (size 7)", + " - 8", + " - 9", + " - 10", + " - 11", + " - 12", + " - 13", + " - 14", + "db > Need to implement searching an internal node", + ]) + end + it 'prints constants' do script = [ ".constants", From e19a483727ad654a7a5eef2a74cfcf46461a56be Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 14 Oct 2017 12:32:37 -0700 Subject: [PATCH 03/58] Part 10 article. --- _parts/part10.md | 493 +++++++++++++++++++++++++ assets/images/internal-node-format.png | Bin 0 -> 144787 bytes 2 files changed, 493 insertions(+) create mode 100644 _parts/part10.md create mode 100644 assets/images/internal-node-format.png diff --git a/_parts/part10.md b/_parts/part10.md new file mode 100644 index 0000000..8bf83ea --- /dev/null +++ b/_parts/part10.md @@ -0,0 +1,493 @@ +--- +title: Part 10 - Splitting a Leaf Node +date: 2017-10-09 +--- + +Our B-Tree doesn't feel like much of a tree with only one node. To fix that, we need some code to split a leaf node in twain. And after that, we need to create an internal node to serve as a parent for the two leaf nodes. + +Basically our goal for this article is to go from this: + +{% include image.html url="assets/images/btree2.png" description="one-node btree" %} + +to this: + +{% include image.html url="assets/images/btree3.png" description="two-level btree" %} + +First things first, let's remove the error handling for a full leaf node: + +```diff + void leaf_node_insert(Cursor* cursor, uint32_t key, Row* value) { + void* node = get_page(cursor->table->pager, cursor->page_num); + + uint32_t num_cells = *leaf_node_num_cells(node); + if (num_cells >= LEAF_NODE_MAX_CELLS) { + // Node full +- printf("Need to implement splitting a leaf node.\n"); +- exit(EXIT_FAILURE); ++ leaf_node_split_and_insert(cursor, key, value); ++ return; + } +``` + +```diff +ExecuteResult execute_insert(Statement* statement, Table* table) { + void* node = get_page(table->pager, table->root_page_num); + uint32_t num_cells = (*leaf_node_num_cells(node)); +- if (num_cells >= LEAF_NODE_MAX_CELLS) { +- return EXECUTE_TABLE_FULL; +- } + + Row* row_to_insert = &(statement->row_to_insert); + uint32_t key_to_insert = row_to_insert->id; +``` + +## Splitting Algorithm + +Easy part's over. Here's a description of what we need to do from [SQLite Database System: Design and Implementation](https://play.google.com/store/books/details/Sibsankar_Haldar_SQLite_Database_System_Design_and?id=9Z6IQQnX1JEC&hl=en) + +> If there is no space on the leaf node, we would split the existing entries residing there and the new one (being inserted) into two equal halves: lower and upper halves. (Keys on the upper half are strictly greater than those on the lower half.) We allocate a new leaf node, and move the upper half into the new node. + + +Let's get a handle to the old node and create the new node: + +```diff ++void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { ++ /* ++ Create a new node and move half the cells over. ++ Insert the new value in one of the two nodes. ++ Update parent or create a new parent. ++ */ ++ ++ void* old_node = get_page(cursor->table->pager, cursor->page_num); ++ uint32_t new_page_num = get_unused_page_num(cursor->table->pager); ++ void* new_node = get_page(cursor->table->pager, new_page_num); ++ initialize_leaf_node(new_node); +``` + +Next, copy every cell into its new location: + +```diff ++ /* ++ All existing keys plus new key should should be divided ++ evenly between old (left) and new (right) nodes. ++ Starting from the right, move each key to correct position. ++ */ ++ for (int32_t i = LEAF_NODE_MAX_CELLS; i >= 0; i--) { ++ void* destination_node; ++ if (i >= LEAF_NODE_LEFT_SPLIT_COUNT) { ++ destination_node = new_node; ++ } else { ++ destination_node = old_node; ++ } ++ uint32_t index_within_node = i % LEAF_NODE_LEFT_SPLIT_COUNT; ++ void* destination = leaf_node_cell(destination_node, index_within_node); ++ ++ if (i == cursor->cell_num) { ++ serialize_row(value, destination); ++ } else if (i > cursor->cell_num) { ++ memcpy(destination, leaf_node_cell(old_node, i - 1), LEAF_NODE_CELL_SIZE); ++ } else { ++ memcpy(destination, leaf_node_cell(old_node, i), LEAF_NODE_CELL_SIZE); ++ } ++ } +``` + +Update cell counts in each node's header: + +```diff ++ /* Update cell count on both leaf nodes */ ++ *(leaf_node_num_cells(old_node)) = LEAF_NODE_LEFT_SPLIT_COUNT; ++ *(leaf_node_num_cells(new_node)) = LEAF_NODE_RIGHT_SPLIT_COUNT; +``` + +Then we need to update the nodes' parent. If the original node was the root, it had no parent. In that case, create a new root node to act as the parent. I'll stub out the other branch for now: + +```diff ++ if (is_node_root(old_node)) { ++ return create_new_root(cursor->table, new_page_num); ++ } else { ++ printf("Need to implement updating parent after split\n"); ++ exit(EXIT_FAILURE); ++ } ++} +``` + +## Allocating New Pages + +Let's go back and define a few new functions and constants. When we created a new leaf node, we put it in a page decided by `get_unused_page_num()`: + +```diff ++/* ++Until we start recycling free pages, new pages will always ++go onto the end of the database file ++*/ ++uint32_t get_unused_page_num(Pager* pager) { return pager->num_pages; } +``` + +For now, we're assuming that in a database with N pages, page numbers 0 through N-1 are allocated. Therefore we can always allocate page number N for new pages. Eventually after we implement deletion, some pages may become empty and their page numbers unused. To be more efficient, we could re-allocate those free pages. + +## Leaf Node Sizes + +To keep the tree balanced, we evenly distribute cells between the two new nodes. If a leaf node can hold N cells, then during a split we need to distribute N+1 cells between two nodes (N original cells plus one new one). I'm arbitrarily choosing the left node to get one more cell if N+1 is odd. + +```diff ++const uint32_t LEAF_NODE_RIGHT_SPLIT_COUNT = (LEAF_NODE_MAX_CELLS + 1) / 2; ++const uint32_t LEAF_NODE_LEFT_SPLIT_COUNT = ++ (LEAF_NODE_MAX_CELLS + 1) - LEAF_NODE_RIGHT_SPLIT_COUNT; +``` + +## Creating a New Root + +Here's how [SQLite Database System](https://play.google.com/store/books/details/Sibsankar_Haldar_SQLite_Database_System_Design_and?id=9Z6IQQnX1JEC&hl=en) explains the process of creating a new root node: + +> Let N be the root node. First allocate two nodes, say L and R. Move lower half of N into L and the upper half into R. Now N is empty. Add 〈L, K,R〉 in N, where K is the max key in L. Page N remains the root. Note that the depth of the tree has increased by one, but the new tree remains height balanced without violating any B+-tree property. + +At this point, we've already allocated the right child and moved the upper half into it. Our function takes the right child as input and allocates a new page to store the left child. + +```diff ++void create_new_root(Table* table, uint32_t right_child_page_num) { ++ /* ++ Handle splitting the root. ++ Old root copied to new page, becomes left child. ++ Address of right child passed in. ++ Re-initialize root page to contain the new root node. ++ New root node points to two children. ++ */ ++ ++ void* root = get_page(table->pager, table->root_page_num); ++ void* right_child = get_page(table->pager, right_child_page_num); ++ uint32_t left_child_page_num = get_unused_page_num(table->pager); ++ void* left_child = get_page(table->pager, left_child_page_num); +``` + +The old root is copied to the left child so we can reuse the root page: + +```diff ++ /* Left child has data copied from old root */ ++ memcpy(left_child, root, PAGE_SIZE); ++ set_node_root(left_child, false); +``` + +Finally we initialize the root page as a new internal node with two children. + +```diff ++ /* Root node is a new internal node with one key and two children */ ++ initialize_internal_node(root); ++ set_node_root(root, true); ++ *internal_node_num_keys(root) = 1; ++ *internal_node_child(root, 0) = left_child_page_num; ++ uint32_t left_child_max_key = get_node_max_key(left_child); ++ *internal_node_key(root, 0) = left_child_max_key; ++ *internal_node_right_child(root) = right_child_page_num; ++} +``` + +## Internal Node Format + +Now that we're finally creating an internal node, we have to define its layout. It starts with the common header, then the number of keys it contains, then the page number of its rightmost child. Internal nodes always have one more child pointer than they have keys. That extra child pointer is stored in the header. + +```diff ++/* ++ * Internal Node Header Layout ++ */ ++const uint32_t INTERNAL_NODE_NUM_KEYS_SIZE = sizeof(uint32_t); ++const uint32_t INTERNAL_NODE_NUM_KEYS_OFFSET = COMMON_NODE_HEADER_SIZE; ++const uint32_t INTERNAL_NODE_RIGHT_CHILD_SIZE = sizeof(uint32_t); ++const uint32_t INTERNAL_NODE_RIGHT_CHILD_OFFSET = ++ INTERNAL_NODE_NUM_KEYS_OFFSET + INTERNAL_NODE_NUM_KEYS_SIZE; ++const uint32_t INTERNAL_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE + ++ INTERNAL_NODE_NUM_KEYS_SIZE + ++ INTERNAL_NODE_RIGHT_CHILD_SIZE; +``` + +The body is an array of cells where each cell contains a child pointer and a key. Every key should be the maximum key contained in the child to its left. + +```diff ++/* ++ * Internal Node Body Layout ++ */ ++const uint32_t INTERNAL_NODE_KEY_SIZE = sizeof(uint32_t); ++const uint32_t INTERNAL_NODE_CHILD_SIZE = sizeof(uint32_t); ++const uint32_t INTERNAL_NODE_CELL_SIZE = ++ INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE; +``` + +Based on these constants, here's how the layout of an internal node will look: + +{% include image.html url="assets/images/internal-node-format.png" description="Our internal node format" %} + +Notice our huge branching factor. Because each child pointer / key pair is so small, we can fit 510 keys and 511 child pointers in each internal node. That means we'll never have to traverse many layers of the tree to find a given key! + +| # internal node layers | max # leaf nodes | Size of all leaf nodes | +|------------------------|---------------------|------------------------| +| 0 | 511^0 = 1 | 4 KB | +| 1 | 511^1 = 512 | ~2 MB | +| 2 | 511^2 = 261,121 | ~1 GB | +| 3 | 511^2 = 133,432,831 | ~550 GB | + +In actuality, we can't store a full 4 KB of data per leaf node due to the overhead of the header, keys, and wasted space. But we can search through something like 500 GB of data by loading only 4 pages from disk. This is why the B-Tree is a useful data structure for databases. + +Here are the methods for reading and writing to an internal node: + +```diff ++uint32_t* internal_node_num_keys(void* node) { ++ return node + INTERNAL_NODE_NUM_KEYS_OFFSET; ++} ++ ++uint32_t* internal_node_right_child(void* node) { ++ return node + INTERNAL_NODE_RIGHT_CHILD_OFFSET; ++} ++ ++uint32_t* internal_node_cell(void* node, uint32_t cell_num) { ++ return node + INTERNAL_NODE_HEADER_SIZE + cell_num * INTERNAL_NODE_CELL_SIZE; ++} ++ ++uint32_t* internal_node_child(void* node, uint32_t child_num) { ++ uint32_t num_keys = *internal_node_num_keys(node); ++ if (child_num > num_keys) { ++ printf("Tried to access child_num %d > num_keys %d\n", child_num, num_keys); ++ exit(EXIT_FAILURE); ++ } else if (child_num == num_keys) { ++ return internal_node_right_child(node); ++ } else { ++ return internal_node_cell(node, child_num); ++ } ++} ++ ++uint32_t* internal_node_key(void* node, uint32_t key_num) { ++ return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; ++} +``` + +For an internal node, the maximum key is always its right key. For a leaf node, it's the key at the maximum index: + +```diff ++uint32_t get_node_max_key(void* node) { ++ switch (get_node_type(node)) { ++ case NODE_INTERNAL: ++ return *internal_node_key(node, *internal_node_num_keys(node) - 1); ++ case NODE_LEAF: ++ return *leaf_node_key(node, *leaf_node_num_cells(node) - 1); ++ } ++} +``` + +## Keeping Track of the Root + +We're finally using the `is_root` field in the common node header. Recall that we use it to decide how to split a leaf node: + +```c + if (is_node_root(old_node)) { + return create_new_root(cursor->table, new_page_num); + } else { + printf("Need to implement updating parent after split\n"); + exit(EXIT_FAILURE); + } +} +``` + +Here are the getter and setter: + +```diff ++bool is_node_root(void* node) { ++ uint8_t value = *((uint8_t*)(node + IS_ROOT_OFFSET)); ++ return (bool)value; ++} ++ ++void set_node_root(void* node, bool is_root) { ++ uint8_t value = is_root; ++ *((uint8_t*)(node + IS_ROOT_OFFSET)) = value; ++} +``` + + +Initializing both types of nodes should default to setting `is_root` to false: + +```diff + void initialize_leaf_node(void* node) { + set_node_type(node, NODE_LEAF); ++ set_node_root(node, false); + *leaf_node_num_cells(node) = 0; + } + ++void initialize_internal_node(void* node) { ++ set_node_type(node, NODE_INTERNAL); ++ set_node_root(node, false); ++ *internal_node_num_keys(node) = 0; ++} +``` + +We should set `is_root` to true when creating the first node of the table: + +```diff + // New database file. Initialize page 0 as leaf node. + void* root_node = get_page(pager, 0); + initialize_leaf_node(root_node); ++ set_node_root(root_node, true); + } + + return table; +``` + +## Printing the Tree + +To help us visualize the state of the database, we should update our `.btree` metacommand to print a multi-level tree. + +I'm going to replace the current `print_leaf_node()` function + +```diff +-void print_leaf_node(void* node) { +- uint32_t num_cells = *leaf_node_num_cells(node); +- printf("leaf (size %d)\n", num_cells); +- for (uint32_t i = 0; i < num_cells; i++) { +- uint32_t key = *leaf_node_key(node, i); +- printf(" - %d : %d\n", i, key); +- } +-} +``` + +with a new recursive function that takes any node, then prints it and its children. It takes an indentation level as a parameter, which increases with each recursive call. I'm also adding a tiny helper function to indent. + +```diff ++void indent(uint32_t level) { ++ for (uint32_t i = 0; i < level; i++) { ++ printf(" "); ++ } ++} ++ ++void print_tree(Pager* pager, uint32_t page_num, uint32_t indentation_level) { ++ void* node = get_page(pager, page_num); ++ uint32_t num_keys, child; ++ ++ switch (get_node_type(node)) { ++ case (NODE_LEAF): ++ num_keys = *leaf_node_num_cells(node); ++ indent(indentation_level); ++ printf("- leaf (size %d)\n", num_keys); ++ for (uint32_t i = 0; i < num_keys; i++) { ++ indent(indentation_level + 1); ++ printf("- %d\n", *leaf_node_key(node, i)); ++ } ++ break; ++ case (NODE_INTERNAL): ++ num_keys = *internal_node_num_keys(node); ++ indent(indentation_level); ++ printf("- internal (size %d)\n", num_keys); ++ for (uint32_t i = 0; i < num_keys; i++) { ++ child = *internal_node_child(node, i); ++ print_tree(pager, child, indentation_level + 1); ++ ++ indent(indentation_level); ++ printf("- key %d\n", *internal_node_key(node, i)); ++ } ++ child = *internal_node_right_child(node); ++ print_tree(pager, child, indentation_level + 1); ++ break; ++ } ++} +``` + +And update the call to the print function, passing an indentation level of zero. + +```diff + } else if (strcmp(input_buffer->buffer, ".btree") == 0) { + printf("Tree:\n"); +- print_leaf_node(get_page(table->pager, 0)); ++ print_tree(table->pager, 0, 0); + return META_COMMAND_SUCCESS; +``` + +Here's a test case for the new printing functionality! + +```diff ++ it 'allows printing out the structure of a 3-leaf-node btree' do ++ script = (1..14).map do |i| ++ "insert #{i} user#{i} person#{i}@example.com" ++ end ++ script << ".btree" ++ script << "insert 15 user15 person15@example.com" ++ script << ".exit" ++ result = run_script(script) ++ ++ expect(result[14...(result.length)]).to eq([ ++ "db > Tree:", ++ "- internal (size 1)", ++ " - leaf (size 7)", ++ " - 1", ++ " - 2", ++ " - 3", ++ " - 4", ++ " - 5", ++ " - 6", ++ " - 7", ++ "- key 7", ++ " - leaf (size 7)", ++ " - 8", ++ " - 9", ++ " - 10", ++ " - 11", ++ " - 12", ++ " - 13", ++ " - 14", ++ "db > Need to implement searching an internal node", ++ ]) ++ end +``` + +The new format is a little simplified, so we need to update the existing `.btree` test: + +```diff + "db > Executed.", + "db > Executed.", + "db > Tree:", +- "leaf (size 3)", +- " - 0 : 1", +- " - 1 : 2", +- " - 2 : 3", ++ "- leaf (size 3)", ++ " - 1", ++ " - 2", ++ " - 3", + "db > " + ]) + end +``` + +Here's the `.btree` output of the new test on its own: + +``` +Tree: +- internal (size 1) + - leaf (size 7) + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - key 7 + - leaf (size 7) + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 +``` + +On the least indented level, we see the root node (an internal node). It says `size 1` because it has one key. Indented one level, we see a leaf node, a key, and another leaf node. The key in the root node (7) is is the maximum key in the first leaf node. Every key greater than 7 is in the second leaf node. + +## A Major Problem + +If you've been following along closely you may notice we've missed something big. Look what happens if we try to insert one additional row: + +``` +db > insert 15 user15 person15@example.com +Need to implement searching an internal node +``` + +Whoops! Who wrote that TODO message? :P + +Next time we'll continue the epic B-tree saga by implementing search on a multi-level tree. \ No newline at end of file diff --git a/assets/images/internal-node-format.png b/assets/images/internal-node-format.png new file mode 100644 index 0000000000000000000000000000000000000000..eded419399ead9a38fbec60b913ef68b9f577b5d GIT binary patch literal 144787 zcma&N1yGzzvjB<*cXxM};O+$1h2ZY)?k)+zHE2R`cXzj-!3pl}@OG1P?*0G!>Q%k1 z+S=Lgo9XH4?&)cpC}l-yBzSyyFfcGASs4jcFfbTRFfj0ESQy}w0*u5+Ffc?6D{*mU zS#fbvWoHL-D_b)#Fqx=it#|5QhOu)x3sKQ^NFh|k9D*?qW_-{-A-@+xPgEwSgu>#l z`M#l}Tx}?+C5owwKx)%p!5G{wjH;roje`JC_WAB55gTOqrw9vGyz2*Rfz{1~Q*o0zC5C}pX~&e09KK$$0kh8e&c z->ZRSb}v>fxQH>S;$GwuFDeN**n;_9^d&fHoml&D*|RR)X#_wG3JyY~uPBXXa9K5r zd2rkG{Y5o6>Jx-WaSec+7>r(ZZ4BZ)%7Dse=Ql~*S{_v4P)}XO3HeBk zVbqVs0XPwNjcEr3Y}$-#v?+V!7|vfsvNFdrt^$|`-%_${hldCUs#G5GrvO$Hj;lXv z&81}6u(q~)uD@GGa*=jWZVVk4-3u9@XMC+XGl_xR$N+}{qur>2pdg(ONh2GUJ6AK5 zPLbI~IGLsu^#`;;e2Xz3px-Kh?m7N0BbjC9YrhD)Hh}f6KW@2W!&c9*bA#=0JOn`4YhQYzxQ^*_d_OI_Vuj%|y5Y6x5p(4!95VnBs^ePhxMQ zXQ)Leat}rilNik=u?&=wnF^*R2b5$!Ng78t66Zm@ zCg!SsnlAZgN0PKmj-URM%IqU2zjR)Ca+7ZE9%!%W=jkWlu@4H~1xNM_gZ&l!~lyIgbWnASFpH*NqMpcQ>?3{%7sr^@>^u0wo9N2Hz|<43cu`KX19A4f_ez#2C600U^kv4 zxGwa`2DT^IQU1F&s24+2L^4=0F-usYX;DNpjy@PCi4{}_O4uj~e#)!^SSI3x>9-qGWPH-3@_4Q((Gbv&> zFU*py1${MAGw*2{<{Hr(t3Av;pJf^x(QpJ8lB36#jhhiHW+qV1)_PjZ5PGIeYfG>Cl#CojOHqt%sGt64p-L?T7XE1Sh_=YGf z1to@OsCFp27_2lS@mL-;z_#|5t56S#U8NMJ5$Nx5Trq`BXCfE9O_gN{i@=NZQh@_uM zV@U!@`N=$TE=ihnCz{rpEyX_zI3-lR0hHTlFOuX(j7Oy8h;{^a$aczWYHF%$S}#q_ zf-cE-;zr&jv(S0a1r%B6zPc#yMjLQu*( z@51O~d$1^Oa(0yGJ?~xZ?Q-LOP=AQDPp}`czjx>`BV4*}(ZzbsHN*+9aGd$N@X3DP zj+Rx|!gJ&tr5JlQA$@#hNMP`odxis^D-rjE)0zXHy@6Gcm6^SQ+uu}-&52`}Roi05 zc*eonl5f~G$Ey6mkAsSXo1Y@BKA%UEg#cSdK`R0C`+ZQb{JPfOX_>*}%E zvYIw4$vWrSk(y=eIr}&r{*PDX4CR$eU*9vohsZ2{PqYoYy)pVi#a0VR%pa+#IjFa zfjfOYMOaN}4Y+o^7`o&6eUbXD+#+R9Xc7m5iQ1XwG-vU|?707O`C#r~{95s9ZFBX& zh{yh%c=L!fvSV!AOlNQk>CuO9y(6|}OO(or^ zc^C63F{OL^Wc1f9%`z)3WakLJ^dZjSmElFq!dl5%J&e)bQlhc$>X8=VW#1ev8$nvxeF}Os3&L?TUJ}ez6ze>R>=T4|3p_T zPaJh@-?U_&qjTGvrJkj7m$E&^+^aU$rt{S1d)?cW6fdbO?8&2yGwy&r2vgC=;FK_G zgczJ#MtE)Y_Ou!vn~O8aTge^CvlL1uE9Q9?ef5*~O*(q^aDha8S=<*W{Qu>H%5mTDjN9o#!l1nphhE<@*|G-&8( zVw8|`=>@nP`R-iT@z&B$Q}CEjCa81Uz3eZW8?MmySoB~!9B=d2`_)q0n29=%ogNmk z8X4lW^eW!h@ih{^tUlZo*fHB7E$CKq)wecsd&nR2c&+X|rV|8lGp``FdfjOLERX*M z*Lb#;?)~kGdG9pFXX#FVoux_m+HhLDr*}r$I)R?}NU-8DVovywUyo18723UCD9Y#Z zYv$_9Wy$@)Ue1<9odwn;Qtm>VqpwpNcT?j_0$sp$+n68kPp!*ukGUW9wLADOQ_l>_ zUxr@i;jLip~8@O)lVWj&#ij)qsG7i zyM2WCR0SI-<0-ualZn@-zWAYX?Vcf>?x6CwQE$n(Bj2&~W?tRpf! zBBB8qC143@cnxyHHRpMp28+CH{jBOs{%wxdhV%nXREQn2g!l(8pqBvWD5LEH28K-q z`hv@jp3lU=j@ii6!Pt!1)6Nmt8w^aqlMmRmGjlZ}^|Z6Kcj5CC zB>&Tc57-7hW+5m2)5X+eVU{4ByYh>);<|;@|4w~qHK7a8v^R)Wkne1KuSr%}CETA_mY|N}I{{tI1Q~>mp zPua@T%oZSFWoKsZ0-QsL?L9lYz@GvCuUG#&<$ny-{@;G6ohMuvHjXVBf}=#<8<J3NU($lk7E!^J$6oXz)kEr;sRwkd8R`GHteLD?e zAm1Su2DL}yurRmf>9>1t@xPEkNOYxjHM{KfNj&D@*5+Z7lVW^5i;@psixPQ++V@Em zA{ZfqULCen{pQK>MmlxPK}l+$1RO&~ zJ;Lb%5x$OrfUY_Aj3GKZi4NY&qMSmnW<+jC4TNe;ji4|)h6aTC8QI&5vWKoG*SqOKv>lOk3Xafu%D_DZD>o#{(p4-`W>T6Mo|%)Nz=8Mk;*RSG0Vzd zg7gfUPE~Ja8-#P^(KakL>@z(4ANG*YAfw)sr+OhMpC(*%M!^8Z5#(mOcd;-BP8BteT0Ae2r|L>us5Z=HS``S}zf1jJ?g26E?Sx9q5fyn^!P{7fKvVafLIr7o}FWY{j zfTL?(MsVDSf!IoW2twhILH=q~4G8czoxXKjBr2&gGPpw`*)^w3oTaMp*x4DbIJkxYPNO~)dfqyC6e7boy<+CY>X8n_0q z_1!p6g8VBfkOL6!vi|`84~aX62h#Tvep77$w3HxniNMfg^13w6C%7pev`$=iLstp;3#=V`b*>B zaAVcuFKFD3*R=dxV-0?jhsC_}vYZ4HmDsO_IiIR}%15tw?vu`fO^$txSIzm1=iS8o zkJkm_m*QqKyd7d`97+Mu2=fRBmA1r@2+B{c!q=&XLItjNg6HgN_5;N zELM1ixI%s@w`^7XMg~iC5|)yDjCtTH2IU+B6x!lXradE7)cSy{OJP7`cBpXiPJLBk z?6`}x;l`uEl+g;ct@~S7lFDwId=D?D-AT*`nGcQSpZa}Jr>efb0lx7+>rRpyg^7=Z zCbR9VR&$*|+qTFLE;(EQ0J0wjZZAU6?#CtTG6=%@bNz;psNayewC=i^B?A|YI>z|l zic@R^KmV@=6`6;vLV9I$(<`)bd2)Ql%vk7=LvNV86f^9byf^J(W4!pDSbwo=a=^e3 zvB~zHyq7qp{XIH=LY(Lh;|~=p6qN32g-XO<*zDyxW1{kaGv6iGVOLi5Kl{Q^3b^Ch z3i>A|OW2Lfl+BdNOnoBWXH#!q%(P)^<{0&cZth>5P&I|wOR1mvF7uBe&%WhswHoD| zzg$$h+I$4u^b{R|JC5ZyIlC=zJ6=9eueyiDR+svBg}%pPXB1DDAms`5vHCWLzDbc5+K`OJWaIeC-V52VcL~7KK4cotJGq#(d@l9iy-~yn(x8c z5%b;s4QX`0qkZwvHJhk*Lz8{00n>d(p8d>=lQ}R4U7^Vjl_CHy!dfH+{`gtZp;WIU zZw7&PK<$)MpZ^&hoLspN*0Cqr%^y%!xYBhV&OB3}4##kX@VzP#`wo(@_5Kk1$t2ei z%DY}_X+hQL?pZOG=mM+4nuL>ni5p^1s(w0|Ylrk=hMn`-whd4xRU7|Zvm(ifYuA4) zUsG8A;MWRIg6iK5O{xUa{@*;u8=VvZ!#FWm<943@Q$)WO*NhsqGpnn$y0J9f74`#} z*M*XV2vSJMj0=R97Z_un77EaQn#Ee~P?*l6=mc4I@)ZY!Z`~G1)Eeq}m1>GRTKa0K zyU~W{dPsb=o3hqhOJLiZIxTOz6DmCVggh1u5?MwUe*^~IfCs8FOZyY?rBj0_PR7!> zzWPWZ$BmV2y+ux#af=hID}E?RLzjfZ;!g~hW-@@y6rRp7tcp%w9L*e>?V;ePbEMT? za6`7V%W3G0{A~z-u^u^a15$iqpvsg?75Os*5qwpZv#BQ1y_5jjLVIthpFEhesxQn8 zajw{cQcKiUbeI`~M7BOsKLR!%8K!zh+GlB}4Zq*hRaHIXTChxp*AjU+T%QvjE)a#r9Oq%jpl3TB3_yJ; z*X%&Cepl&sVf>5AuXv+7G#>uLMySh2m4+*IHsOG>$g!PX#j7MBGswRT?Pp`~L1`H# zd~1dnRyzMxAW@xCZ-=MuEHh}Zj!^!Q>BFgXZxu^(>?h`=4;|t9xxUmoW&09{d9iW? z`0wL?Ejz)^ec){iabAMu4Df!St<(0Fu!OjpCk*y*^SJ=rMVKg$nX^{po@>Igv^bl) zi}b)ZZe&f7HTAU&=?0Nqu-UIEp(AMjN)~-+oPEf7{$@u*@kOaQR#}}k6*Xp4!zWUy z-=E!&A_cJw4%SS4S;lDlUYs8}&%2~?P0H%Y7AF1eigD!#aY}HQzK*6-2Jl&yc3l>r za`}}5sA(fbI?y&kkqLI;kI7x2!+(3O_@;YQ$mf=*;?hTLX!m+0Q5 z$FN;vUGedYKMPr*ya>}C>C;70qoY8MdqoYh1=)C*wprf|4;E2ITiW}&N^Fw2mT+88 zdssGR_x!oAp%{aUA%fCNu zY1^;g$zvE8hn{P~uGr`gc?{$Q?ALt=&7WEdwvNJuX{99V;Yt8pHAGTba>3^&m@dB9 zF5u?6skxIL9~FCQ6HDH@;D!0*$j>&o!_I$31mM$ti&Vs)` z##WX4rLBBC&yOP|p?lABC|{x#q%&MS&mHu`GrEP_u99?2HA&BCx24U`GJtuKAe-F8 z{%AgSpo&Arm7@k(slU0v6#>;{Lg*ZVZ{l5j{n7eiHcR(ZWczaf5|psUeHjDw4XEe^ zX(Qc;owPcQWu6UwR!o+eL#=1E5}FvTk}OojiBQ#h z%cG(bXI86>e)+}jyXR?P8rOrse{jiVn>i5`7zH{gp_4QLQTH2EGZhyJyLeis++G+x zd@`8>C=+D^5xv;aVRFc@gD-h;arBn<>^Qpmi3k-f;wfL>@ zL7|M7Q&`RklJ=5bi9KYuVs{nQjZg%Km%Fg(LB(VV7rTWb?ZL=16Svy%;m59%=M=MG z4i}AYXNO|hXV>@0so!w6wIyEM^)dJoP{)9Yj)bwS$3ge;J87)~u}Wxi z-qz1C7nA_Av-3m$#fB8)sGlf0@Mn6WxW3w#$oV1@w-6>3EWx#o>uxW!8THp%SkkU; zfCor6Bg^iSD8d|JFgpvqDJ&KKA(A3bn_OGt9|TzrGE0;63*8~A;+FU z65b5zpXYHdJ#tcPcKH@f-BiapfLi|T4$;wUhv@hy5qFR;1kXERTm(q|;puh4-Sm!W zz_u}*HsagjXmG2CL6g$L%)Fk(bPBW_1PuU?v>%#QKqi$!D&6z$>+DF3v0gWPq1wTo zj4vi6N~MSjPYJ&%@%vVU3;8nT3Qm`Tz5(`PS*1|6JLsMdi0mXA(BB&EKHvYw*PJAZ zt$tTX^3Aw1t}lDr&#;m_F*12ac;y**%;lxs0+rriR4sr`vld@pzatuXwxfSRkf4CG zmWQ{-8pfCAWLg8@zlomFIo?5|i|QSNe!6(`WU6l71N#=8bBXuJl*D}OqLd?P6Y=)R z_l(A~@m>s5F)O&Y`u7!nSv1xNto_3xz+8PlI+=y)qju?hZ?%^-i+ZVHL}5gAB;rBB z)JbBX_OV;QGbT4!!wB=kXgRLM((S1n|O`3Fm2FQMLZ!!%2ln=&(StpHC z#jfzJ13AG75tLaxNC{o&C>GSzfC5C91?cxFcsDxx72{O8!PUH;U_Uxd5(@7}BeQnm zr~D@SsxcXOi7gb7pj-1f?u_@Q>q|;K+Z)T~F%ZN$LO;xTqz5Jj6YS-qZoCwol120K zpY@iDX)^D(lrSLMALk%yxjT@xg+3i@e;fE38{Nt(Np%vnJ3vj%^H5?&Hxudxd$J^u z8cHfT3v`VBSh(rHXaE(4NK&>S#EMivHNNfH`G~{*doMU_PcHO3WRJ@9unD!o#eT7O zEry3#_oM^34{`mtY)xl;oF-t)VX{8tHVIM%G2-;-uLAEH=L;k@3-vc3Y>%i;h+Zdy znh8uy;C>v3c`dh#lYM_Gk=1u(c8CbQx4E|uW(f{RVvLJvKaG%Hc&cd^zm;o?eVluW zw$qtE4vx!%obHhFx;?*f(S5Pv7P`y&j$u8Y6C0#eBu~7%n}&#}7wjJOYs%zDPS8}* zy)lH+?+zJK{W%kI@9WbOC@$fL*y=@gDl(-O+8P+u2R@SCP@vrX<{fftS1U7jjX zn=`BF(DI@45K@+`r;fN;5L!`or7@%>wvbEjAPbmd%!+G0>3Wg2iz0bfNEE?ptlz^0 zOlUO%HX&D7*1B{v{k|LL3~|{YM^KhImX5(Oi2cywaaq`*izIqnNOpkSWYfGXKM?Rn zdjGV5#Zh1p5bvKS95eX3o*U}GagoTZ@q3x+^moGSQ$0Cs>e^@RuCZC2F4*MLzwUeF zO_(Q(g?1YbLkZVGC22Sm>zy82YTs4mZMH<`vTm;L(pbDKU^o`F8DcaE6$=`39)C+O zQo{s=ThA;AAlNR=VI&uFvWehI{%LBA#+fife!0*VWG(~usU9$5@|#)7TH!+=23jcQ z8T3(_Mzr(Ur}f{JBy|we{xfT24pi*Cm#k6&eJ4teC)kxc5$}BJfHbtXj6!Q?^z3TOO}jA8__%K_ag!nt7YTPf`J%^hQ87t< z@smGGd$sAPmu$!k^K7~6X5u(U23B#h(|Vz*FN}fZp0doWVyf+@ZXTVSl~T^S#g>Xz zwkPJmE^FGj=!LPE$~XL%st9d#1Qv8nOxJx%9H2FA3@@R$kNS-D+GCupo@A0ebfc!- z^-!$Z<%MwM-{fS=LkPgrkM$fkfgz5wqF4|SL*%$4Xj2wA{-O5Ry+?^I2BnGZE;0hxp(|Y5@@Kt0{x#pKHeeY>^6!)zRxjM{=MhXImXH# z5eOWzo>x(dwDE-OhY7|yA0L)Ze&fpsbU+}rj$=%=r?BG~6EB}ZE*L~KtE9axMW_;q z?(g5QL7>31g`|~M0(K8a^>+^DgzxgLvsH|VXm*1cRr{gS?NRZFfMzKg5nBSICcC5r z^dD{IB_4>u7Yd+x2l#sHfyl(7T4CrZPPZZ8kewH0Qqj?JAHD6p>cZ>)x~#xG(3jgY zHTBUMs~DA|>6MQa&9p-i$|!$$3LjSB^H|0B*xZ8C?qO-+MLqAyCW@mi#f^4MGwH7$ z-A@ znJGd-PQW$#yKclj>-hClUys^*+oa;i^3?c3`S#W;S;N~8#ItjoSl`j%U5jc1Hy$vu z_=y7~x3MI1iVsK0EhAH|p7vR2E$u4j$!> zOKi!%ybK!9X!Yg|LR71TrFGwY!$iz6dD%OezMC@GTH6d79h*zdzVO32B@&5(5jAeT zNkgca9fQUbM(*}Dzna*zG^mdix{U&^3a)645s3h@6rYP8e_IW~wP57K=Sz zV3Wo_@{TORG=bc|skh8xWH^k&s}r9WThTxU!2*Jz197(Uz#ccQgK?eDyTmv#{g4kv zd-LJliFLl=$Tqzu^^9OJ9j4mT(aK?v(8<3P!a_{iv2#n^}*ThXc}cR@x&X39{T+kx#P<<>%0 zUOz9k*C9A+#36FHgXnNN;ofTZ7)l8T-|Fzk+d|xUD}BPZIY}0C;CMQ7Vnh?W zXH!(634E1Xg+^G7XsF1D1FAgfF9M446hn0AD>``m%WCq>670eL8bPEGUQ)n_ynwjP z8j_1x#8FcH;=I9$ktwzJy}QUdNp7@Hghk~f+IC(bf+EW?QI4Up4225fbSlm*GRkv{ z=pUj&LC()-x$Ch$M%Q^*lJFqMj3D?#tXu+WpDJ3PHu#?+(=6w#8FMO(st=pqH_x1( z3(v4BwqRu9P=tko$f(o@c16N!lak9ED5zO+StC{Ntw>9= z@><=py?)o_>YiKd!=_kYy3Sb>j&_Na<%+c*2>U00Qe3qi8s8#Lo+cTwoM1w)d1KE2 zlhX){OR>oVO32|Ez(}rF9d7OAtq#B_bH!&9{Q7z5E4}1mpX-q5o>;hw- zNr92NaN96>^#4|WR9RBTX7(Lf8jrG)g<4!}=WV2A$HqO;gi2u^HamiEcQm>I@ZEtig2sm<=iTc_{iwa~ z<$PzL86d;EN1J1fQZ62PXTKYGr1AJ0b?}i5f2BNYk#`N#yhG#jn^N2B8U4!BbDQ0> zfxrC3{C>Sz$PdkzcDM=`6f_w4?9el75uf&L!&E&3JyK-;kFUg`=s7fGY&JLKj_V}n zC|roC`Ke1HX+ANM{NtyU^;TF#6RoKBh$@BOU~7*dM=TUZMeauvo;a$Xly0o5Xb{&= zLU+VJ?nL*?9j_f&vbx7C&TT0$4vah%zwD{_*-BC*1O0F!-aHD5nB*aSCbrYBzu`l1 zG|xk$Xy+)=BpXP*nv(nF@W_GR`X@@5x$UyTrdLdC2ACeXxCvs<2tk94R|c$p`c{+q zv{$9pL#$;j5nh=PdDq$f`>G+%HGzGRvjtIBP~)R!GH))-Q>!Vv3L`%~<7Q zV`u15a8CiEYL^J0WV*qEdSMq6p#F?mE9JN)C5K*-B@{`-pJKctMCYGJhWloQv-yjM zVM`7@=4j?qzuUAh%96i2CHA$Q{URrag2P(>l>duEMHc?GNd8$Xih4 zVkWtQ8%ElDr!*)*n)&CXBBXJ3JvXVA6w*@5zNu)3GLk%lm){r3rD=xDjBY9+PW)Ac zo{j244yAFQ#snmw(M~^xy!8xRt$q!Opbhrm4rR=C4}{3hb8@VMcPrUOqeYc6K|(`_ zpv$jb7e4`s1+%C0qZ(EE3q5C{G_g4dIEEExC$T{qj5LO-yhCh;Uw%4dj;)Ru-OUgf z8Jmp4P#%T^mEl{8BGM(ca304kf_TXf|4jUVF;+T-|G_{g0(Wrd7Kb*QzyQPkN3^*8 z3U7_C3a3t;A~4^pb0k{}tW5Zd3JfI2D?!{E$Dcw13>FR06D8HzEd(lkNxH{g0e zkr^A1hkDYqPOy%u@yVvydwWcsQdQ&~nFGHHmW4^|x@+ zbxp2u>XJxlYU0Tf@E!-Je+J`Qe5eb|0LvfTTWN+6*-=xT`#5*f2}-n@de zaYJ38yat1iPRhQQ9KqJX`)`si6$UxO&H90h|G>7ip^%a zY8&yw)=&oQR6c_Nrp1G}J-ivabkuDCx%rhZ+@V)M>EtdHXiLMpBxHw`ZrhBs^epw( zrd{;Qt@u0%s#mTeh6pkcGgRtTeO{tWtDl|K=t`X>SJr3f^_qubG9x2Fy8;qcpR zDz~@>D7<@>7o8wKwACRZ2@|jsBVMC)XM24CiqUK&(O7+2kYm@)w^*Wl9Lvz~U5VA; z%;+7L};)kcU z!ZX9W$p|JTXu^s{vW5Un8cb%&%rW1xG-7yCYRAg)Sp=*y`QH?TuMyWNa&BM+LxxWn z%&RHr75|1hc*%f=lj6S0swV1WvB~*N`uSy zv<>0Y56?nwWDV8ry^o>{X*O&j?cx=krwHWLc~4Kv;XFa%MP-7X@cQxe#1_Ok;>umt zE8eB(hdkWcg{(j(Qj zQM?h%4tRCLa&K*vU`XExfyLi!nm-3d>*+~|bA;jTEQrieqDoZinm$nFT+v z-?vcrLynL8(H`9-d+O$EgBkAy{6I}$mQWDCD*4#|d|mFIWS7y42UKYEK;!V!VSit| zY|SDWB`f%r%n;Z;bMlG@ZJB(i?`Dx5#Sd>_$ZtA8X*b7oYbXL{3w9xQ7H1TdcbB3B z={7ZCfgF}+@b+5TYHe8(Ra~lGbVKLBXKRen_I;`L;=az<2G9yc+>p@*)|o^{!v8sP zF2g~76VY{=JK~pB&zM+s!t@>F^~fvZw;%Hu%kT4l)Y!O`y)Fw_@MB2Fo*Lb-X<8#i z_oZxlr|S5&VT*S0wz$)d&`P{3THFN9!-0@V&b-?RHCVU+i>&FzN;N6k8fZP8aDk6 zdy_&m%LDP&g^?SVh2qH*mpL~#9z@SxBr6mctbhxd+WBauBw=JmZi>KSM^H*+u(eb9 z_*79ZU9for?vS6y(t5cQ@SWS4P4V5)9rA-GQ*fZm(O?HmaF2^Etg~dUTSCA65fE)l zb0Zh7=zcFe=6z+@GY2CS>meb@}@8Yr{_GTOk zB-Ln&P1grN)fc_~eW9rvEjFXTT)cBt81;jYKY$}HZl@-VrkUttWO$owU{b*)Hy#4M zTUQg#TeKHdMttArYCV;r-!5539c8QLek^mA=Dyx3cux3ux$P&`mqzMnKa0BI0Dr97sx$^| z0BqPS7AqofY9#GgYqNF9?c$qstXO?lv}IJ?D5j5l3ClZ+^}Gknw`GWiCPw!7p#t3} zix+O+^%4bByZgP+E(N5SqyrXqX&r1r3R~GCb(e7vZUr%FlxrNICaT}ju=`fC#ak_k zc_fe~5wRa56jGM(J&4T|aJnKR8r>}q+}&Eg^u1w*o3E+4Y4>=(Yi$%=LdVzTZNV1| z`F3zAl-{*c>&0_0aquTVAp&(70SuNt^e!EHv!QB+KA-@ju2I=PdLPed0K$_5{zruo z{2b`0`RO;|G4tNymf{+*sO&s8h?}nR`|3ZPJPAkwjuD?Y{hqPNl&ba@J7x*u)@vfD z4}>}l1j6DZJ3TzNoN@7&s(pxALx@mdPK z!aJcwn>}yoeu0DjP=_C=XCfrIeRquNamp%mM@SJx&&cw!$a$z|S@8<{Yks>(zQLf` zaR=(!xg@skTjTi)x7y}Yx%)beU<~$+IbAYr8gN@;sVU>Z65(k)*uRi@YO2r|IzQC4 zJ{c}()WD<7JSv!^u-KV#8#vS5PBgF16gdaS?args9TEqAkeM|rCAuGf1y|Ea(Hd1% z^1%$TuMn?dQK*b2z`o9Gz*Acu*$oSC-t_?Gcz0pM-ldwj^4v|yHqFcL`CLBf^Smc6 zD&4M6((ds5?hA|z_Qt{!^>;Zs8|hccHoL>>k3TzdeL0J|`>_gp3=p*^KLS=dnvTSV zCd;F#fU2<~Mb=PRQTf;pDJir7RvrL(RBi*k8{)jN%OXLChx~DGCmfDLF1DukN9F8) za%QRE=?0ldjDq={PLiGHE-kNf$WVFIheN*VCdTqoEaPzsj<@Hd-y>DlTz6Z>#QWXS zRYdKA>9)&vFoN7DmcPGqRH!tPqxtLP>4MJD3d2GvL^xbnqVT2Xhh;zM4UOJ-r5TdJ zYLV0NV7I-s&9TdVJu{Ni2R|r{(`YXiH8(S_ZYh^PX|IJS>_@SY0+rE#u2|$p!Oy){ z?^y42W_p=N5+6B4zw`RgK}`hXnvBFW6J3>Y16;{c^qopIgPIzPzx8nGc$+=A>LFqY zO>gjsufdodB*B`7fkOII#@Gxd8P^iHPt}*1Ef99EyfBR=rC3}s+$Ra`+Y$Lj2eFL2 z*P>e-2w;`XKJbtN9aGxnu-SCRxz8>rnOe+!UCB>a+o+1xjd;N&9XXCAg&j_I>Oo2} z9bsW=gC@s848HU}&gUj&06fIWLo=*{U0^K@{2iPjHUkH}D;Bp}TdLI8rP|(ooO2>A zuqx}5kjMuL75;*6LbHk04H^vc9b+q&03Px!Yf60l$D5xG8JAD8vDWMo*QLxJoZ0>A z7oSXi2((6=AncMlcI)$gywk58b%cVx5i3BMZBCy~9{U{#$KV6GY;4+xvFMr}U%T!e ztU;|Y#*`U~@wA%TU{pM*ynVs$ zPxb+)K+6Qnh(5c4_K%Y)0_B_vZ$)bBkk@9tFUkW^2J;OFrp zEKo-K&~Nd2el>5!j1Jo>6~ZIyE*laaN&G6JX2#F0_2DH$j(Jw?qkVWHejmytkz3{G z+;G|ss`^7Z7aK*1y_2XQ{uO`GPR+2K%-|iH7MxDOmp?<`936E=r&MjE|NRgk#WtE# z*@oXOlzE}Ve5{Y+aK0{(@ z)YeNjtHN|3&;8&SO*I;)h$YZ}ltl%aO#C28zzYHag1bUPtu)^5GTAK7p3rEqACEu$ zNlHnPl z1AFN-m*^M2?`-HGYx>UUR}{hicP6%c3Iis)EnKy&T;uB{kL}|1J#>@j8~)_KFsjjd z*0;uMY|o~kj3lnE>Ck^aF@Io4@}9+hzl165YWV6O-gGbVoSTirY0Fr&2yk90p1Kd1 zydZr=V=O!Ql#&#^AfHuWuM3Q2)`$WPFdx?EmYa0_RXPYYNCCQ2 z`d681sgZGv`?`Sdi{HcIxD{VdBWC&!d=mMS4D=X)rR03KKRJMEK!UUt3jIuXAh%?m zk@=A9`k7tAws_Ho!s+=ZZW3J=Hu+LE%iQ0X9rzVcoku^Exf!Kf^_1H*mD2?Uq}x>P zuGRJ%eFWSMuG&y2Gk?P-4}*q?`vQw82IA>Kevz@pSp)6`!Bxyq`}6L}K+-O8*;XSxq~>$*Wms6Vjdf1@j9$pdM%TSCU5IG#p6Pf$Dm_ z+K7ea?P4bLbN0PU_$di}-n}2;Y>2LaAi}f+;WBY~Of)Bk_{!Y4%ju0b{}3P{x?x5d z_M9D>EY=ao=?mN+V~JgBQFE-$M+Pm;{74CX%cA2PHy7olBJcj<0xX561r?z0_oQ7^ z{ZpmL2XPaEy&Y*_(sDL~mIq0$%nIV>!qMdw}J69!UI_Dj~>*6J^7oW*8e`Z1*2QHN~B?w0JPcjofC}8X2D9%P^rYi6# zS*Y&``S&`%4Oj^$h^@e+f;w)(fkJJotc#K3w5u*jeSv52%14!0SC!icK!v)Yqn6p+-CR$Nxh zUXno4wUK`~0aiha{3ky?fi7B@CgUdmHPXPjfPcTMq1^|y_8BC$|Ir!$Lf91tn$cI< zHW_q(aApDRf>hU%6LKXWn&h2*oZY&>YaepVB8AldL->CXAHo7+_#0lzIuxW*4R1lg zjHd)J@F6%5)x{DHGZ27{F|#78oc|`&*A1Wu6m3+Z#eMptjB!Ee<7!3?01QEFGE5DJ z(F_DV3&aiy|EJ33UkGEMYcuAr;9n2^$QAIq3Q)5`C1$WG0g*}DYnd&p1!cR+F7jvo z9r7>Uzlnh8T*foi%Y&%IN&^}&Ydx#$k`00ppM=jC3?fo+jGWs4xg8Uh1nlvz-a`$7 z0L=i=K+2ib17c=Ry*}r%7Lbm~TD{rce{Ko^>G3abyV8JT2jpqPKuZN4Vu~Q_I5^6s ztm)C!fY)3Y7JunvYDUOm@j;lgls%^g7j~*GwIl~-w=3j*6vlUa1;yk8c+^<_F6UDx zlxiXd*~-bsPJ0xa(dUy)?|+2<7-TU#9e*$O!kD6@67NM_Y_mtm+}a%vHdlgz07Tg)!A!u-i;O=radB1b+x#zC6f6UCXp^-LbQ#CP+zDaOw6xXwFs;&wt~ zCzDX@-Z83u4F=0@)_d6H*UJ`7y9+nP;2GT00k1L{smLPLv2;-Ihb)J*Bzu3^+0#6F&h?+D}=umx6;vDTQ! zVmQv%p2)FKPM^br)NHhi=8T1Cgj+&khs5rvzt3o&<}M#%hZ!4%r~;t$I&WbE?+8WI z+Q}aaY2m)itdU8(T9HW@u%_LAT+~(8$(%42hr(iYVpw{9w9EAOw8qAh7HBSR_+rWS zWV2+9^KuO-**~h}F*hdm2+*2oHxZ9f7zQ(~`7TnaA?ox8EQ$DB;f8+{GuOago~?X8 z%)ijBRZ-rMX5D^n?4BK!!L#+-YzhJSv;#`pYU|YJSH1|^s~Wzl)Xw{XgoHa>-{lku zNV2LKZM*!s_7=o%1^NxSAIcnzFfah**2LmX$XMDKUo#{}MqyDnVE5zD{8JX?Z3Zg} z24YcjNLOHgzve&1o zqNu=%PJ{U)k<(2yEA2J{xvI=%v^8!yxsOee87SxrT-mxf9K{*9t?)>mfMj8C_ve{i ze{ziE^W?!*YPhq+8}#Bx59I{~3cFyi$|$utLHWL1Rt@&2SGe3e&pjag1f5T6l}ivk zBF_eSy4IuG@M)F-*bcl~R9f5UTD#7S7c*%dJpMPH!(qPFx?#3+dGBy+-cl|Ya4MaW z-T1J#uGIzvM-$N}nLQ?0K9V{L8A4j#i?!(Vv#ycqe!IeSxGnG;nLir&{!6<-%w|Qd z=Z1M}^S3Zuqk)h0A-yauU>L*tP1VL?vgc$^05^6H`kz+O*v+> zh53Jv2`{qpSds98FB)^|-bi%{Qv_&($o^t1PsclH0pqZ8EDh`!RR^x3hdx(u4|3Ig0>3#5)eHAvbMNkS$^8X=Z=m45Q#QZ zk=xRh;DtDXJw8xeyd`zLcuO4myW^yIu(t$!#_ndgb22w%E+i3AlV=azAJC8!s}uOK z2WPW%<2HiHIyTP!-F<^!#YP_sL1cD6qc|b-2r8ma(=5Z+crk#QR9zp!j3L z-ei)2qyBgE29h1iJ6#rQMuT0{)t~S83!lCMfW{)j?wb)uhn2^Hz)rErV!dMPs){>7 zWB?XnT-JRWpu7<+>}2oZDp1h)T4+@l24K8C^ z&A_(zP=qUE*`v`;6w6d`Ld|5H`xa&LXjWJf4Kv-rAPv)Fknusv3TF4X-I3`WN;ev< z=~m=qxE&d(dw@=+&D?x%)D``^J0BD0v4(2v;F6J|TH6=z8=(L5qSp^PU|VjDBu{hz?AjZJBi=~wt}pd{*bDkcWyrV7kqP%f1xiH~)~0b$1&aShJ|SNAu?c}gSc?GDa?BKYn1JlyiQ;}=P{ z@l;w!qE}Cb_CBg67!p$n&I!5I7u)PGv&5wRlNhoogq$S4!A<+xA6ugPRCl}WR@l7L zZ;JCLcp2fYL<@Q9;3rNgiXAmZr%yDp+l67rEhh{_1VhnXynAI`DR8zv+8et*5&mc_ z@3ZfeY$~qB;j&+b9KEtYe$-ir{1ubDI+`h(i=)md527o>QfJp5dHLCBXgGrKV8oJb zu#rNH>v@nStgur&?uWO)Q^)m_e(H6hmk2pfb)7!x*Dz(km?h}HdI4;$UzaRkoFGml z4U=%1Fntfy=jHZgexm*IdVM-ZWPg_aeNnXCA%k^e!eYPfA}^Js%^jtmi_uDr+Z88r zg54)JmALi*71FYU8i7IB4L#mfq4TSaT0GUirK2ut_S4J!0qQGIQP5N!5Y*- z7tRt-Z6S6%7y~=Bt+hIe9*NMS(-q<~Uk`NcA9f zvAI%SeRqrN^*yyWI4CWV;LKt_&%>Kh#R+-5Vm?-PK0X{)6EBubA3zL5phcs3Y@WXN zEZg}>cqp~sN7A7r?B0Gf&d}3emlo-|Dwcu3y}mV$J69&Ofy@0BE>Gd4{IiR+YYyhf z3Qf&K2JG!uj-#CMzvn2)c)*pDboi{llIh+}_EW{319iLYajc+ zfg8_%%QHS+JKGQt7K|)@C(J^JP=&+%XlSxnjzZ^!DoRE&p|wd zb&x}UxS*y?b6*g7Y)-VUERNtFU$Zy%<6Z0tUMpe@J1v$_=hPp!Ql1dgeU|lUkn2d; zF(N&NEOf}Ls$XSv<6v?j|?o}|J%h-?GX z{PENaF({w_^DnZ6G3Ss`efu0PnSo8;8LL8`_@x$y_<7)9;cjdxnr@8B3Zktkm`%YW zLY-Y~1ziDlOU4f;bG1Qo^P3e&C`Fs$CUNbp++!kK)6p0D4^a7oWk|Jw)1>i-%61hr zcMt*XG>wgWqWaWa5k1(Mrf_m7m#LC z1%4K+`GO<5#jCFzBS15GK?1HT*-^uNOmV%-IYRuMNR{5ZkzZfXmL@9{=|G?q)5@N! zyy#R57+>J<87Nsq_vT%ZLHJ;m3&Z)N{ept;TM(K&S2d>^L^p&gwrKJZ9 zy$e6{3bgT^5W|UCy>hiY=#Q_3>WyU@n56|VNON~>jiBmgzea%WxZ_UbW_LL}USjuW z*2UMWTJQYBxpE&1U{n4By2yKP_0fLfdViR@DJGS^;c+L=c%Qim@~2_tr0hY<@P+@P zyZq*e2Y=@;B%!^&{RhH!bCMP=)AuiOVprW%`#i4M%vI9+c-RGcwW}MCqcWhH>IY0i zhEO&+N?6KOzF?~MHWzh)dKgI~L~U5O%Jvu8-kl2nyq#EsDf9*t;saJ{b(7RGp50ms zo_Wjt{bt(dJJurG;!16T70Q1zhXycj$N50X@d3JvfWT#&{fi_ZE6n~Ydv)O9PY2wK zisMuvCi@jozJ$#{4bNcRY4>|^V_&O^{X~B`a6*WNKKOkyNzD|=u~l;4Gh*n*j8lN1 zTDcHDQWeUKyqL3OFO*L4276RqmS8KLDa4C*a%p-?kHzZJM zwa+NTf5&8&Oy$y~CAwT=x4zveUut&GR>p()`5lio3>dtLM00*0IxNh^lLj55epFqK z?YD%MvkTG*+XE(7AqE$b0VsFqYVHF$glmmO zgEuD@s1a`?*7%^u9_%nx+mda2C}UBYmdi=-5f~6szJTjj*zp*&yaOgjUL&GCs}@7L zVqm<5`4Dpspj?!uhHWy;pVE}t?Ok{x#oCyG^ZYMlTozWMr?i2q8*cq{P;XfiPwMnpcLk82rggD}@cu1R1 zr9S(78nw*3f!&wjh;?M^z4P!NJrPvbqfvX|w(k_!yYI-_U7}L+ohcf!H1}|qedP*kZ|3aD65>#O`^1c|`mI#og2q z#P|T`01vM=?n{*BOx4}&5bFDff^~YFMlK@+rIVcH|BZeSQ!0o5eK#V_g}x44C>2BMZNH0 zYp?FYTnh}v$;(v{L~S?d)-Eqb#FFaiLs4DY{8>!HX!;IA;D)t^B9yrX6CA;KVcwc_ z8KfHg8CgK2JyY|Fv7Iz|22n}Y(8Od$)49svPK3QOO0oJL9{pIf%fYbLJdR7V*}-^@9-x_hp8^(NfXAuP^7?StR7k?7->1q5}~%#n&CX1cYU z<2cE%w1rQ>yDntm}Z(Y4Wlsc%nH=5>=&OG{pmP)ax^-X=I(#opB%Hquj zbq9A8?_MevgVvY%hh}kdm|H2n9i}b`093WOd!G2y#lGbJLS1NN6{mU%A#xM z0OeM(JS1NG6@9C=oUe%_T2`A@0y=9pxFf5I z1eRko4a+YU-j1oxTl_&77>tzvGB59rTkb_lsMC=SmrL00+&}TNassuEw;uhn(!6)n zgF?)~+1YAEC|WIW?DXZ^9>hmcoWt>?+a=P zXJz^XNGwgkb;+0oT5N$Y2gGQLZW(FyvL;)v#RzE*o|=qFHD$eydBGodU~%Vq5v@|w zGFoPXL70w@UODh=Wh#beVkQ4z5Q2YPeOMGpARwbdbB_+^%;;C>7Pj$%`8cXVlA%NM z)2;sg@W4J<009xtRF`66x^_{u#tO^i;;@8fRTvB|n99kar34{G&-Zh-fHTF)`ZTyLY(k;jr~tVEd5v0y;85KTR?`*Bo4~ zxLbD^Fr`u8E&ABy0O}Qd|2$$BsI^3Td?I`|E=$ZU4@rhmuGkpl?nvwTT-*i2s0i1IwP}*Rpe3g`yP@P9!UOhXhvjX%kfG%idrqXihi&{ssFRO^|{AN2e%HeKU z2<0Tiz1DS&%aWRi-wtE;bXdwW3@}@jOc!kRi0b_@vME6`VZ0;agua@OOBt^BjX?D+)rH#FwNCYqM=GP}|^;+;P z8cph5l`-2F%Mv&}>ra-KpXD%iSthi3lfS|bJZQ`XC$;G@JTL73?Hgr4{N`rHx{Oz8 zUId7HkV~3^BU&;TxuFyp*O$i2Co*@-YPOcjb$ifpD%&QpJfDB2jUs^v3i~2krn0llQ9 z!X~~DSoDP?`8?e$taR6<6xFQPg0tv?OQeBmU<@6 z&->`7QSLiCYm)c#4okfr;6R9#Wt%w4;P)IyvVuy6w}sgpIQdAeEIWIulR(bu%)3q? zOf#<>ZEtUaG2CmGF&tDG63Im|!6O8d<}eY{C#zSLbP%9Q{+12+4)>PggJU^iNX2L2 zJ==s8NgNHrVk>^O3skP;)sG}v6Bu6p;!Q_=VPiIDrg(`YMV5VGg8=WcEZjXILi`IW z>u-HX;%rtAQ0lU^8zVcoZi+!yUA&BE6>!bm_rl+dk1T{8X49gm8ENxew)Obk^-K$g zB}7TNKJnw%5X1pRqvvF}(Z_R*XZI$G{2IrvKr*>jtjjtWYt@62q6}3t5%qgSWf%Pc z6AOE7aopj#Iy=g^S5G-_!S|c)OIKr^%S;>EjFw4DW0jfDP0t8j$9CxkGKfPhy$dU8 zdZ3}f&l-$04q+`EBIHt;>e3YBM>{4nKY86b z;DswX{xH2z1Uerwaw>cSaxl(s-pL;=wSU`YA!r!5A@#iFu*q536t}cy z^2Py&cVQ3g^!stN0YPiDNa#%?Ttv#1KbYo*RB~Q?WklIo8>3%qC>bidW|l@<{_<|a z>rdw2eC*6y7IsF1*LQCK5xMQIJC>ZoP;x;)aKxyJm!S7Wrt0|W*N(7LI!j|u=cUaY z0bfpEfPK^xRJCy ztgA>ZyceO6Btxomh1cB&j(93&X5=3zBoaAvzj`>u8o)ca`q@Am3~cz+ifV9c3;j^Q z>Y4_;2aBLegXI^2C|;`Ons~@25i^UdLQXQ)R!jT)FJ9A2ND!Su#+ClUQc>Inf~V@E zpz6u0{*wTkU9*PQE6M5nQ&Zz+l(x+!=zX>sMgVbJ6xbg5Y~=T_+Fo`PTo zU@KcG_!Eqk+cnp2Hq}tKad&lz!{~%e3|HMq`Ly-wKz<3JBxfyq~U73r^%Ldxgc0@=NngOw zt;<~BCTfJ@!k|vrXPy`pSCe)YK~otAvAbw5z4zrxYOR|#51s7<5Gq&nfQ#y4mfup#kpJH;rnpw9d+3%Q;s9^ z=;3UXjo1^>tM&-*Q=@7F6?q*{XJ1x8}5yln0vWIu0t9VP^pTK1P1)D2+K+O^)ozsNG@> zI6Ua#r_@;v{oeOj<9OT2Yftu`X_a%K^Q$XSc1*j3iqt|`dv6ez=vKe>)P!aeOnjXuBg1L#Iij-S}d4W=yI!4wl1KCx!d)n5JFz zDMSn|&ee+KsKpnW##GWV+(uMe&Cq012hAw}p3QpvA>|Y3Yy0i{<(JxeuGb}{hh`A;u1n?w6o2=RImY$&Q)4Y?=*TAMKUU^_771pe^cs~}=d3zSRu z*B%xfvBSp54Fn8r!8)T60`H=&{c=TRFQOcg?oHR@>h)SF>j|q_TloXf2f^Ke9g?pJ z&4Q{g9kj5@brS%>V)0P!CjeR?b%K2AZ&KD;&77b0a=?l7gL_a_2Ih0MKALp4 z6@Sk_5r}&P9&@hWzih{)9%}_|O#00xqL^E5;q}}1=(QO#huX$@{*^Dj#=fkSPzmT@ z^z$f<9yvEPngv-xsaGKR4@cY{liFZqb$}69&F;PrXYG8Y8*x<C@e_nL9CFMb)jhu40r7JV?yX}A<4gA0=D$u%BoY48$3_L_!8TBO%>?Cf7m%lp3tJHfh2#b1#^cde-5Yl0?l_EoA%Qbb(x76na zfyWAFnK2c5EO}P=Xh>SyJmGXJlxv!mUX~zI-0V-@vM4L=D1!*qo1{XRk&!bh$Hq19 zpZ>-@UC^euzl~fFTtY!~l_3xPHw3^geK?4STM9<75<1@-GheVxsLY9dHYFV#fHp}j z93>Gsm6#{~g+VF?(U+R)5{>e+k}vt@Q@Pcw2QN9btGEd)LBVQkSTj@TAMOIia*=`V zCek)lorh7&$`gO5$bV|GtMRy~+L#Cl2{(Qnd9#gWz5q5#u}r}UK#+(=|I>M{zcPj1 z+20{LSo>`6eMG?yoJ!D`jXAse)gZJ$wGtdY# z$^4uS(;j&tbj&-mxAw`ama*LaT=w0R$*>wvB9kg26TVk5fwVfx-T9Z`s>z9@>*v(I z31Zz8adEfrZd>%D&t?dfM+lOgcvdc7SEKB{C$p^4OprRA;59*R>2zw=U-s19DB2g# zU`DU9B>k`|%pfKDH~19<1iwscC+|7OoS|VWeKieO>k{Lp`|_+~Z8nDCf#@$Iac{qY zWvoL)*J&Fl?#)Q=g;WZR7LWbK(6Ux6xi#eAIC054!uN;ciHT#6vYS0B+Hv}f5T_SB z-uawk-c?MWLS;7*hDf=FJo-(r{;+|K>d0HGD*xLC2Iesd26CdyA@?p%n?lXVQ|12_ znS7(7faLs~IfYzgt4gj~ISMI%iS|j=^t2*a}?yp`j#$ zK8%nvmU=L*#|Gums<9>aC-1I}*n0mEi9l`7`j9J(|u zb+a%B=k!)klCx(b`*I*T>Xdj1rHjV+DET!jE$g>H>(3!u^iONv*(?H1 zBh9xjw!$3CZsUCYd#5DIZ8|tx>dk}duB1LMC%O)xZ}q~M>a}w|PW6E1*DbA zmxw~ZU{&$^!NYh-=nD16lGv(Bi&)S%rWvuP;0jumAJNO0zh$HQ{i(9l*oM?LcB2Jz zf?9YlW*7Sn#?CY*Ps^;}U4lo|EofuuI40*S>dt43cYejFxK|7e$ZBjE){2S z0}wvHceQX32n?EE@>MO;;Ng)UFr6VJ`6xJdKE;Do|Jdky|1dLzZt}-F<&xT;Gk1C5 zTO$gO+(4Q&YhAz)IMcdbEo}@htEb!X-t~!guro&!C0xovU#0D{S*4X-*(CBr1vW4brpb2zgC&Y<2U7!nss>* z+4vJ3FUESOMw>s!C*`wr4-I~Z;sFQznQV*f3~wfBi&V!dUt^7JcZZv%jvW{rlfGs? zaGQSNm=rplC>Gkspe5_Q2}d>Bwb$xO^Cm!>vw`af?=(9o3pzXyX~XfD^MwAYU|2gk zCfM2<|$M4+@<0#A4yHi4S#?%l+#15zOZaJ40L+2FrVs)9DA_vQ4TZGz{ zYDd=<%$XBbrIpwrk#IrsiQkuDnadprs*oa|oJx3YLEe4F1sdXlB{W=4*6ZEgPbJZ` z=cPAmexzs-WjRckDM{g}#%sn>GPKx)F~CcP47USgAJ?-?U&V<-Q;RHne3gvPQ^Rqa z%|n}Wnyk1C2>D^SBn%P#_m3JQm0N9^rlikxG?^TS6J?|n@p?coE#IpX{JA4l0Z6Yq zDIlqLpO`}Pev6A3ZRs}-6RB^i{sd?MkW^vWmp>3# zVo)3+D%qaLH!Gnp6Yx27Mo0II5*^c2O@)RYbr&q&x>t2zdlt6u zlX))VLY@7q5TtJkVLwf>ouG6odNVJqEWOX68wRD2H9_SPi@c-!I=?2tjHQXuZ z5{0^oDJ}o=%4!+qQR)@t(6fHPuYwIO{RP^TyFN>an!8BN?%+o(=`0PU`Tp3fT}f== zn3G7JSRbmmsOD23stCewY>!LAWGO}QC7H|1Dx*f7E!#~^2GrR|nA8xC6$uilt%T$A ziZJTDAk4?TCD+ZiUk^^!d-(5dIDTy(=Qmof8e@VzIYo1IJm5P&l74+tr-Zhh3{UCT zN0!PLoJc|+pRHl>KT6-BE!k|s8&b3`TDqX zut^Wj*9+*b3E4LL0T4pH%C0jVKl01)X?7qi+3ERc>r1kJn|Q`R<{$xgCyHMYYqN0>X9Zn{#< zERT{#!fXEu5IZ#>KzL4T?9b(Pt6d^&o7QZKnR2_Mu>%N{7xt-W{=MN`*|FGW^e@B4 z$QL^}dw#ZIgf#;l;@OK(Ggz>aaQ-EQ3I?)%AC8h3ny_ic>5^h&S%}g1gkLPYunQGJ zL^v>j2c%i>d}~gwlAiBtEqlQ_ACX9StRNrqCXV#&3y(t^f#nvR+%2b`MZw? z1KkXLQ=@75on-tpX9MF(=P^ubph~x*^&Au(Du=vka*l#(@@bjP`m=qRi%>fPN<3Xl z*MjPV^g2Fl_&8Ay%C10P9#={CYRYSj$ANj;rGm&s-Ep@BHP0hyNMKu~jBAEh1`RDg zj?9`ML;gT{3BlbMbF`fP;cj&$<)R|bpTl><%~25mmgQd?+d3S`+ni+i#wkakmcrn$ zX~>oL2pYa( z7J2;k>aNtMnAgK9!Sv6LDinOk0np3B@YO12@Uao;kwoDh-@B|uhQF!-9V|P+gIf29 zFYXUdd{Jxf)N_l--#)17P{G&R7(!FFM94t!@-c<$`?@xjsWBCkb zZkH)LW~xPw&ub|ZKJuCYz0<9ff>-C20;f$OJj3=OGhYJ*1TedDIX|%M+=^$VU?6If z6Y(=5PY>pwHplHB`o;!XKR5;xKhuT_VvO(An{4c^q!0qY?XK8uF> z9QB(3z{>c6hoOSxrvL96{aq|#U`034n7Q9>fGaXP2L0~z?OZGzzJv&O zHsSiu6Pu{3H+qrkFVe&&nr(aW|DTDx{TmvRr=k3_^w*Q20aZgz6#wEtwVJu-M3-HalhFcB)1sZ|G_!n_w_d|8YJ~MNZ@UWPLtd+ z50r@WYq=ErB^ngsp}l@$dQTrNnBmHv0hCyncmJG}OIl2z{2icZ05Cwxu+XI26Vlye zUJkSgU5MtN-=vM+*{<~VWw^!NZiKGWjHd}c?3a7LJ0zqV;om*r>V4Q2^Y=@vKb(44 z;GG6Imhh*MH;d_PoW%S|2hoebm&B)DdUGupDIR}uSOk{a(9@c)O_$G?A8L~~OW@Ar z+DPqvxO2Ja51%czHcuN&`Aw11s^<<84(8k961tZmr++A(~$EN zA+@DpFe@M_lxB0#(C!I+DXx_}vRKAEaD8DDsIPP-36Jvn!Q1-TIuSnm_*dm8w*BcU zw({|5_y$8D+z(1kry{g_cp!v@3cOIewwf|j;-o0yB42`d5NZ| z=BRD1uSN&p*eo;Mf)2bJ2j42L;H-6Kg^j~?E3yCGN@OU2nf`+Fg2Cgq9I+-Vw^laG zzz55Y4xLW1e?gCNPw6R|6<7YnlP~DD(p?&6;Jjwf^syY-TL|yk%o0-l#xnPgsaz@o zj-ke^Lpr_^W0tSEmZ1Jam>JsQLZuH=dxSg1%eHsLVr#-ehCiOUolZr>4xIv^;(*`X z9t1A~mMwKJ?|t!Cm((_Lc@nBit%e*~EYLSJd9LP}-2R4$FOoA1sw?L?NEwd3q`CR- z`;B#WM7pGW(wF!qC|NG`r;g`?z!zT|Kb1}GGJ*zVn~+WDw)0D#Nae;?v3%R(j_2ZA zcd2^{Tlox}KY(#ca_;z$Net}GEi`e9;NcP~usF3fQd{lpKq-ZjGA#uhgBy#XiMdM@ z2*>#7hn1lu94i?Mg|%+o;#{MYJ|q6LUb(7!BmgAy{^9lGH>w`F6N;^9uwQ`)fv#{h zWPQdh+wQgo2W0E6HX?&aaEf$FTFOiV!sADV(C11k~K8;Uqx=jTdyYX7Jdz z#ie?e|FM?;8Z}0SYZ)8EKHhEM_2dG*8{Sh@;TiPm>^PF4rvq>w6usnHdDI)n@`OVP{7m}he=A(Me^RyQ}9ZWG{IL5ep zeWGAQ0zkW-KFjDrmN1peY!zA=?kvX+>YaPLu(Wwu8k}x~(=xnLynubwXxH#y`?Qnv zey{abb)q;eS(%u;dcZyDJW6|4SuqVQ6EqwV#%9%PTDfJPv^o=G;f& zL38eH-Ti1T^h8jut@%SUS4+)Q_2HM;jd{w5iv#GHB^c82%4 z;igKtk>H+Ptg)Vc0W*ECTPr1x3Lpy{@@7hwX+!~Nle%V&Nhu;jOQ<{yn!3^W6N!f# z;sDG!L10tB6AhESxS%1tXVb7h%Dm|}h-r(>o80 z*dF~$%>u_8vO=~&MMxb#Mk{KTNbg-DH#WCXpP=H>Zm#phZSOZ)+;w)yhF=m3ErGtw zy1ldJR5fWbFf6;Tpcg@6nskwed(yVJ5fl>n}c{uK7 z!dKko?%|hc&T~ok;UfWTizz8xV&6E?gtu)Qo!b>CYbJ#V1ibekyJx6t1kbIDv^59a zA?xfhlzS3SgyyW5j!6FJ=E2S80oF<#-a3H>2{=TCniKKVHsZP$EncwZ-y9p)DVDUl_(o|8WC zhzRbqmRB%wj=qEC9;JNmlRsX55$ToF-dzMz04znGz7yu~j`=RK&yyotOQ=t|M?CMG zD?XwB*?Q@ql?&X)0%%(uPpgTne>E~NMITl|@>-Ov%xCkAO#SM*vDB%qWKFIO<3cJv zm?*`|CF6-20tGn2oTK7qy!mE+P!O6RcwzyhTb%2jw)}`vqgORV{P0zhI*bX%1{~UP zn_ju3z!{iI3#8HfZw8#Ck{yOyTmsM*I(#^DYVI|rgHBb z#^SVE&^@+fD(^Kkc}!t7dW?Q2o^_0`f-JPFuYMe4$LH@M$(SuPtF=c`D;-51;MGA5Zs`zv8Q0zmnI~uhZ zCky}v5EFEy8LvtO$_OM^WJ*70&g&%3+x7Wq&Qd-PXPr%<*QI%1rKMZ6f@^Kgq&K-6 z`cCKVOab{mk^~iE>BB1+{np{NmvjqKBPVo2Z=c(&zq1F7U59h72C)*>UKsO#*C1jxnVnJ)aJQUjw+t>Nr30W#>jD{;Z@BqyLgU6a)J{1ObuHAf&+R3T;%h%m^c z7okb=Np|ed<|oj{uIx|O^Sgh{F*y}&3Ms4CUKC*69WfKazN-Iw>UoO|$k{XwL?#6% z1@U{{&w%?`T9?EmBq{Pfm_>E6yu+>9t2P??qV{#+yjc#NI*7E^T@0&jiRPDMSc-?F zf$p=JWv0-HG1C#w{cZa%$o6Tw-$f60Yn4(~{SDfaBzWUVH zIK};}{CM||g%HCc@dW2)zFn%_7&ABF2n;-m>+R}LGiIzTYjeCU)TpgejnaF%^9gk2 zVb!|bXt=EQjEfrH5D?%cvz>j{(tox~s?j~A77j|YgyRgc7;Uz;xqno_OQljm~H7 z0T+pO8HFQIn=wi{W>d74@2PbDqPN<{_Kpu4XhljT-TNE_fo3Z|TIjnWDzAb}nD{Av z#``u*G|`*>pLu3U!;V1-_234DfM9Tn^ErDF0p&#ok zFY81Yq1dPO__lCdk-iI|U1F=H-YFuK)LH!#67Bo5?pAeK{!>bDV*1wjfkjl1QF#(9 zl(xTzuX;oXw5+V`_wO#BSligQwd*-`_wpQ4dF(h~fGr5mIEPmbIQ3u2bpfs$7 z{ICm~;Mh()KYB@o^$Va8uWDyIS|zE{B;*mkj<)V-NtFcpgRs@ip@(V%X+6Odk>WZ8&cJkAVPPq9(xOcwZk7W2Ry4GC> ze4aw9rFsa>-j!Q-*l6^*BXFK8wc`7CrP!v2>*Yn}Jhq)$^W@gt{PpGGG>}hdZr5(* z8TSxUqx0Dtj`eB5B>iE^H77nZ_C`1TwT)qgq~yW(9?IYdeuC+?BgkYL@bJ#JP=}LE z&8NCMWBHUeuMl4+I0acNonwYQtbdQ=t$?mJ$TrGp5#&N(vyF3ZhZgd!yQ=tmUW2?;G`tpNqSSMdp(|7CK=>l5R2Y$+g;A(}pdK&w zC+I0Q9tZOt#(08lX*7g#!QJtF{}iXly~-caaoR zzmq6lm*?rVUW+5qnA*{KMp81C%j6~O3ww9K)!g(7^+5HjLi7qlEhSm#-P8#Fd?dkk zMggy{y{bmv;0u#H)9*OpM?g~oFR=zf((BiOG^F8h!v-JFauBN$apbp#L!Yo7CVH|b z9WC3BsMs$eZE!)F2v}sl$JF^TU%jj_J`^}gD@9EoQm8Px`&%w8wr(RQ-=K&sO`w$6 zP?xWT=$TxS-z;ct;BlxiS#kP6tGBDFwa@ux>XXEn1*DWUewYrPS5O^0l&W%jXrQWO zL1Yn+n_6m9`vu!csPZDVAQCA_TPIqkY<@$1Qzoz%>^?J!8cW`X-x9% z8Qd@fE}hgi0=&xMaW>X2t9qFa4tJkZ?DHi~h|vFG?=8FPSfX{|Kp?nlaF^f&53WIi z%fdZ41a}DT?ry;yf?HU)1qtpB!6mr8y|VW{@40{BjypzwSRcB3RaecLHRXBcyh%_0 zC9b(vR)BSEyhzyMoK#Xn;a)*S<&b&=s`ynogdY`8Rslat-eO5 zscVP(pMVP{#Lk8O&N$tNuHKtIrbo{O55A-*nGyawaBh7$Nq6#KVNCh<9J>h0xH0lB zppN*H`2mF!b6yykWDC4;!&Zi$3mQTQ)v0=mpwW0JD@eD26(l$gxADbXJ&&G4jaoa~@~ z_UPDb!DMcCOC(~d#VcTKQD7BZ^w6zTZ#R3!UVRUH)gR_X`e1}U{AcRk5_%90-37YjZ;2 ze!N(Z%gmSKf1$df9G{#r-Z{}v{o|#kM)d94dU@bY;3A=o#|zT z<|P=3!t2CPW#-Um6|1-1`%_{2KQYZODL_oqRoZmSC3u27dCg{D4!P>F87q_ySh(Q$ zFM1Y~{Dg1Hvr(>Jdl%|l4xgzr_t6r(Cv@qiqo%D93#B*amw?e^5=YlRZjr@h!W_P; zWzK)iXQ!Cm_6Oa>we}qaXpAjCa+%c}U2?`30rp$qmAytaIiDvp_8@uue8hyN^wXfD z)9+e}y&QLH_z+3D&6tbY%Zt%^s@esbzN(HGBX5ISuZ<@$yQJQa^(479;5GkgYBB_3 z6e*_u15|bC+`#?G9hC3do)Rb`wKu&~oTTBInR*yvop?qcsnw8cn?kaaK-mA)0?=Ty z7Hpknj{mro2J<>@-$%KF2H$PFsP(!1>CM(wal1W(Ja}V|WmCddG$m|lMs!Up;LA=u z?pcDhOywm1tv5%xG6v_nj<5buCod{*@Rm zTZ?Ec?K*E@aSWyY5Nmzo?O#p6rRC}Pvnfq$hhNwhmS7lzM-}S?3B~=47J$T%g~<)z zi77bd@^kPkSLsmAO~`Yr21D5O#zH9cvX=#3&n!l&X?Ky(!1(`67be@Iu*a5;JQv@* zQj4#lSW*^GFvQTs@O?kaf%T&EV{rm1xHyQ}_VKC#o*cJzetNde#5?f2$j`k=%+qfUXM!OyxjSXuyQjAsYF0Z zk^39H;zkc!+PmSbR$IuwT+T=7HevrcA~PUIB*ME0u6iWmo%6A9@^tR-*GQU*=JQqr zyVG-EpP``ztB7SvByIlc>qN&mo^Yz`^HFfPwJgRb8dUQ)%TuBYhriD8qI(#6%<#z# zo$a^KHprD1cFFUbu9%e(o(|L{lj=+rWdb=bWrK6`mlApRjSrm{n-V=G1NpRTt>Me? zSvZ&aB9CT7&$U4)_@^{$Z2>(YF>O4j!_h`8iNa7R1GX-0wn$v@O>r96{qyy$l-Zwc zaW2&_e#QekK$elw%K(N-DPQ!Zk76{Xp@zL$kC^kk9dqeHgS=w@PBq$_7u8f$qvY4& zdsoW-jyQez7dY{s2?m^z>OWnjHjUiG9(*GU&*l965(f=HF}yh8bWO*?{Pwn`{;Gj}{cik;vX57W>p{b@^SWo)#SvLyfy&MRXV3DG@oy)BII7dR_zjFwkW+M4a*4)>5n&W5*` z+Zt_`ke*(b7YMV%B=-g;i5ab2*&J43@nPi5GKKSyr_+%Nk;eTp0l~jH?eMb(Czzf} zMaAA+6W`M7aWsvuchGd8bPw8N7#FcfwNbbP?|O^};qaH5Cn9mMu|bC*>O{{zY0#ZaAmL#_5TVNM)SpVC(6BJq76p5= z%}FCID$9@gW6p80PY+||=fCH9Z&7}F*C(AsM3sBcXzEC-g6w}8?&sqx}5;gW6o;@6uG+Q_?j9Dm}v5>++B}J z+TZ<6=>dfYPpa7J;yyd%#FMVoE;Qz-8@v613ska@>{Vft5DG3!7(7ZysG@;xrGPr4 zm|y9yB(P8E#*h9@ldjKN^Q>{F+jDCnd4WWdq6Fhq70Vs|c|n9_8@1E?%*Em)DckGl z6eGWX6`yZ4GMRrkc;6Gx{MITIW|Sg@USr?#ENR@wde({=n?MwpD4GJ!y2DfzJd z-?*e0e1K)s7pFl2LZdaD^g3*gF2TEt2p!TywnAf+=z#udX999E#z1J60Ay4&ir-qmJqi9jok6!YVVV5`$07xFswX|XC_8J*v!e4GqwXink4^cEoaY2j; zYfK*yH;WtCdD0s4@Bm-Qn%UpqLV0mllj7QRnT~b?r9w+S-BKtSO2Axd&E`V)6L-K4 zEVI|`=u!^n4Lpu)E|gI1o~(UEytCcms5)Rd0QgE z7X66t@kzqj_>tV<0L21hg|NGXoEeNT(@z z5dM+Gqe4f(pH>@9xp;494i~P|HX8awKKZzgACk>`)*wrQ`Y0OGfXgp=aczG^QBOcZ}tLSb>w2%wW?zUXl%|zqiun!0qsD3{tXMm zhO`9*(gz>nFI+OAa&>k#}#_Jol(WGjB5$weZT`IOQZGI87xl1V_ENh9j{ zX;##;lvhMAerXf{QIK2Tz0KI778ljHAZhunkT z#yb|<%YOf20@MMK0Vx@#$^Ss-6BntjjhLv`A?X;tU2QVSJd?Ro>UjC*li8nuI=~LNB^3Y+HLoEJ#sB7P+w-nb| zJ(mi2B4fKuN;@0)6pq#yr9sc^gd=Z+PG4%A++esuFZoT69W*+7GZg z-~8HAMjZ@Q4>Jsgf%t*1P|HbPK}qi`R=^?D8W)kf&{0#MZrA)YzZeqP>F0}U9qa!a z2i^B>$ht5-M?H@j%dd+Frc3PiY}rG8TvF+1xAx3e(CJt`p5y8Fd0@1#omL$0F z3R&I*6iTxcovqd?U90aKu*yD7Y^IgBXuca^$1!j#4@LcaF5UD40Gg8k!ZoxtunNIG z0HTG6u#@XM)14PQpO@H|wHn&U=|A(@RU;eO!JMH`!QO2nreCEN$nvUWqZ;$~X!p3h zA_JSFFeu4-yNlOOI-UzUd5s-4P#_TwBhifb=!I_b${T{%Dsx8eys1U)t~4uBCH4=* z_6;zDr;VC?2Dh}sX}~f^=bRMQ>ASUC+KxaM$J4J;REGn}$k-I)dzM3o;2zq1$G4?i z^eJIT3=Yvm&M`r$DuanulJoc=48xJCGOR(Y@2}e4%*hX%%!mBV<^Q+)MP9W|F+9si z&{=-hnS=JWl|}xJmC_;#(R)&X;XM`Rs2E0Tx(zT_C3@b7L+1O7_8&8kjib1R{aht3 zZx9VjLZtE8M`=CCV{)*^Q<4gn@&>BR>A;h3CRkb<(K#_OXsQd}xBXeK35uU;5(dH<^aVtIgFA;Nd+3ZzMT0Zi1)gl_M43 zQQ0Bya9KZ^Co-JDSWeSLw4eLtI&P?gzUYU=6+U)*VOh<>Zp;i^QgH3^tN+BO-gN*~ zs`N&lqDke)U%<%^U*@a25b%?9XsL>lW4InJ{?=#nmC^V5t}ct2({`z<^Hh@lnKCa& z*_^#l^ID6zR`AHC{a)vl{OMZojaHCFet3yvRz|BxZ-QYO69=&^hN{xMOB8# zTL!Zlhih}EyX{!5y{Uq}MMhcaA2yI&hh$$cM92)JfxU0tFx>r+>Aa#y(Iv!8`baRg ziuC@zLr@1E>Xqs(yO=3L2#U{2-$DI_(?zD@TH zx{2@b+F#|F^hHQ8DmG4E6P_te7#K%Ri6(+fu&8}7-St}MFWPStk$VGcbDq8NU)ViqTg^!SIlFa+Mbh{HF zz0WR(r8doKfJ-8PfP9+&&fZWWD**`68;PIe-n(7BQl*Q>n|tB&SEJN6zQ2Cqc!>5- ziBQs%`-}B50FVdaoOsEWFvxj5Z^SV)lE=-Xr_dfYMW=5{NVt9p?@aZGh_jb2)pgNV z5B>N;Zjk6K_&o*fx?au3uaq-e%8yniPbP2q3%0oy9E#|8E6XPNx$;Ghl&|=yVSN-^ zVvX@3;J^Jm_Ke%3o=D#7`tkV7A7ZAc0GEs%S)QSGHh)5^7}HS;5Qa3i?wXByEJq(psIN=H=Y8h*U6lyIOaD$ zEn$Gzf3yx+#0@?*KuPLOIBncbsiIw)Vk5;QUGD2Qr=6pjHArGw8z~(9u$u8C%nzFW z<+NaSGKYWD|4VYAv6NWCtx`YIvm8mHWd7k$imtrN#$e_nGnV0iR>$&;8TnD+*vW%(sYarY zOj_+fXgg_k#(!xHVl|nW&mR>qlyKtjA|y}J-U@MRyQwc|xu$rLV+nbDA8oVT<|+lk zNM=3}`B>P5m6^c4`ekGC7gZREz{yxj<=;^`LTu2M8}62JM{ISSg-fdmq^>^|sD~=$ zA3%7MZ?m}Pv)jZ=%8#wYQ&~vOptis<5lTMuDMf}y6hr?cR#L3sUWz2AqBO2|=R5BO z+MZG=OCDwo#GK;N)0qi_<$E)^!_$ZPTZV4I;ktoJ?Vc`P?hy8f1jpMxZR4e0bX6Hvzk3w9>N)fdRDHbaKR7+R`YC*S ze6^ZU0v&LtYR49sjB>m(w%I*XgBIq5rG3;Xv?^8h+>xh*p$8IKFRCOkJY6{b_i^KBD+P7>kb! zCpwSfZ0#m4M8aTF0-BFaw1O6JF#G}$9 zD|#NrFmb7jP?<#=XwqF}Au+l^in*}QET}?bCqc}{a|gQ{e=#siJ;6^MdscFmzp%84 zgAg`8Venh9E_U1zID8PS@6GHT*2H2J9Xj1SJ7?A;HGYF8^7JCGrMR_V^Z1*SPD2do z5~q_M@hW`hi;eK8*+xiSYMnfiNIADEI4Me(m*8sTo9cmbTS=0lErIi6b61U^5jL0+ zzhnM0tKh-X+rGQeB0HR}zt}}-Kv<~|w3C1T890$B;ooG4BQ1ng_dUa$y~6ifxEXU7 z6@B<5{-T9|ggL2{ah4A+UF|jeoiG5UQs1fj47KycfdG2%g#Dmi4N+qHj9|i>_L;vK ziSmuS+GQMmR3Z|FAmevCPp3*fTqwaJg7yA2(0w1~AKm8wG1zt8ZxDfgXZf*=2MINRw zlka}6xF$+wU?K4awMQCF{^W>Fz1?BZnpZ7cX3-$o#u(?fxC(1?K-e?dJTDp4$D>uh zF!$NRvsCuBr4U~Y|&@^2%7Ff?+b;ctUhJQjW7 zicqqH)5DN@bDsWjb^+)Jn!hY4twiWn!Jk77OgVhdohYTT>z*K)b|4knIi#b-wB7JPmx9ePXyJ z#xeJw3Ew1g)0!wGNZTs{4uULj(lXZ8#6s*K<&+;;2|!Au@!)rS;Hu zejZ@LSS+`k5O;3TgE%T>aELV5pG`i8^jyUW^FgrT#CE8~z&`+_2LN|@F6IknF(Y!3 z_tXe%Z_TS}>S_0euT*1otj;Qj0QMml9z?Of>e<}JXm-U4QPBCC(=`}zarZHDxlq8> znNW~4fMG{A+vXa8ZhuSx?)^4mv+*qxVYwUVf;s`f1grz6icX6O*&5}2_I4q#;E&vp zyD}=*#er$4!}putVmPyW5-d_rGF%hVrz z?~BoKys>y4_yhMM_@iEVl(~(@5`QG4gs>0dTp}F*qRNp0M7_+LWe)i?h*bU0#ZC_l zKF!Ivg%R?tA58(tQ+~@6@(&9`5_m*?$-XM(|Gyu2aEb+ZAA~+|1LX3PlL1~uvjSp{fV@f>zl-svFtn`h zD$(D#AUgw8v7*8MV-);f$td_pCIVf{HiyBWb-OqE`o$W7%aj_y1OX7Dx?V2FI6#UJ z{hE1n@Lpbf0bBv0JGm1?&zHj7j_vWmMgLA)Qlagk9kZ2w2@4%LWZMY7u1u)2nVgLH zT3cC9=6?B+&E^cFm789>aD9^7JNLpJRUR&gq?zzS{$M%TvvJ+i72Q$Ro$p*i_~eM> zDyq#H%YO5;Eys@eT*z}Sm5_Ueu!|^vc|b3FKdA5mPix6$~FVpE$shWgMS|kCMOW7{Xm{xQXQp?@cU`H3mbO|k2@0wVnOkOB~ z-fNlh`T8YADjv}W?$7rQ-x)TG1OTw?3Gu|%4dEI6EDCMWjLLE0G@r`P=+r#`B~{D) z=8QRpxI84c#BP{BM2G)bz-6E#w8-?baM?-a$5hyBZC0ZRrz=f)t~tIEp?mJ~Na*ZP zvpOg}Gag_e*_V^;!N1c0ujwZ53*@UUZofaj|NVJk&6gL|&(YV3m-KXR$>?Qz z7h6LIb65uln2M0Z)rm>MMHk|4 z8ug}t^cbG>u!lHEM79;+_j^zd&%i53HAXJ`cSqTfFr?{%FrX*C88xV3P-$dn&T|zR z)&*7nYh1jO1rWMj#f*QAntwTT4m7bQh7xWSk><-tkaUQCfDg}XefXF$^&L3;2x^Oe z>pOExfHOwcqvR0zNk1!kGd6FI!};?>=mm+`(JL(2%8ml_q9F4B6N}^&AUN0B>|tkfD(ZaA+l`5)S9Q`@`1|Za72kg98(q@hO?7Qi|u!N`;)WkyzTL7$JG8Kq7 zTiV3E5FuMvvuU(FUk}WHeeKycHd0)fLV+S9R(A7mDNr20WA9>c@VFEnuejzY0t_sw zs#rHY`6QP&Ck(YCm5PB3s@U}>9AH$+uS_ zQy<9kOlJ^iFE>+H>^myofB30^J>;mGzU{dwjs1OCIXRTLu=;*-i8tE$2KBkc2}7eG z3C8#Bv(eVpHBhweX?pha2@kF4Kw*`6QHaHn~&HvX84 zIS1nsdnJ)>6Y4GpOnLXHMkp_Y33BxZhkty2z7fK+*Zy;ZILMIw`+{;P?+4Ld?rCLe zMGDA~yqNPtr6`l4rtTz&JcIN?=nC7RZ5VBfr^t3HkN;#?GxM2Ni=PF_C%VhZgY?|{ zX%JD>ro_DhS<4Z=a+sfLCY@zxjXY-8o%>C(f*y94R9M9rKhY_~ngIyFfE1iGQq9m{ z)5b@=xbFmWWq*W!k?Ri0iXR#Q5C2h6&;R{vHMGS83a@HeX*~S#LNL;dOdk@B093XxM?7@epHNR;bp;09{xy6j;HY2lmNObS z^at%i7&)Dq*ou4AgpIzQ910#e}VCIYS z^qy+Hx!%S60uNphyeEAcF0Ni?Mg6s7g9xEkLZe%NQJ^VGJzcbwZ~C-5#eX!BZjB)< zW$~e=Kl282dW*d43ZT|q6>e+LSl95Rde)tv;MJVV5}G(@Nw(xaPR7tTuo$7x^7WRh zHUfSBjK4WID!HWK^0BdREm8T@U|?#sB=M96V0SSe_;SveRnK5}76s>83*M;i!UQGx zq7_`q64$PGUbE~jXBBTANX;B=_5BCPXOd3ufHp_x$H#a|FNl3^did%7hh-UA zmpw++clJ!15jyUZ0+$Sd-p2;vAhzJy%%o$)-FSxp)`$woPM8tCs;@n+8mF z+esZir@#>e9r3~Om1BQXcl#Ch@l$V-LMhoOfj;3OL1%%$2q>^uneR{96MbFvZ$JCP zs*HO8eEefx4NIvjl4fH6l7q8uCD2X?*hV_=L`A4fVNuSFm-G&_OcEBX0u9c;LD4Y_sPyIN0og~3p~J&Z3;t)HZ3Cer9*_5cs8)ZFs_J=& z8ZqCAXBr5OHtes;S&rfrSnbI#Sd`^-c(~q5-h67(e;-8kewWgxtLL+GUyQEUC#xfV zJJQu^HASu-D~P)>hz{|DLTzLwrJvou;7N{#{RmFDCq#C+%zNsm_&$r&ubIGDflSPY z-?>D=>XveMCwu-eRj>gr9oKW-_TxoetYbcaBVF#k*^FfT0(NL4UYeQ6qwJl>nyy{J zQ$Nzf=|ou_$k#B!;IZyZIIda=ru?p?sn%{^5!pr*q**W2eXC1LRIb?O&ibaw;_IH{ zh?$9N%{vPG6>c4@J(Y$U0B+5Mo!VQF0iTAm|HM#+_At8e*?>j7vC=jpVkOyFteDl9 z`?=rW9f{4Y=Y^LG=hBOma;-eT%p?ZGLa?&U`)huPQ1_s&%SOrQQb0|fw*;-WhsaTy z4FxhN&||4&jbpT`PzdResrP25!|h^a($eqo16jkmDGek@m|$b>p&QM}L&Y_a+oEs0 zOQ^7vz7T}-(obpcp4?C~)$)ZwttJ+$zfGVBUGJdFjbM!g!;Tc>)&A7BIi8TU@l%L! zhi#{Y>>frd$=aBR{g#^~uuIU^Fchzr)@8lnpo1w0)mTO_sn%VeN{=P?32KS2it<3> zIUi^d(~3c=hGl2T=N0YE25;(NVR=_#*Jvnw*FOKZJkZ2FF9)}+ZXK5vP>AJU=9jfJ z5K-_!C|RZS_IF&nvwum33-scFKT|hFf5&Txyit3pLRM{mDL{<+%�+;rVog6w5sg=AO$~5 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%0D}p*HDrdOEAwMA9b`_tBG}= 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 Date: Sun, 22 Oct 2017 18:33:18 -0700 Subject: [PATCH 04/58] Part 11 article - search internal node. --- _parts/part11.md | 113 ++++++++++++++++++++++++++++++++++++++++++++++ db.c | 31 ++++++++++++- spec/main_spec.rb | 14 ++++-- 3 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 _parts/part11.md diff --git a/_parts/part11.md b/_parts/part11.md new file mode 100644 index 0000000..13a70b5 --- /dev/null +++ b/_parts/part11.md @@ -0,0 +1,113 @@ +--- +title: Part 11 - Recursively Searching the B-Tree +date: 2017-10-22 +--- + +Last time we ended with an error inserting our 15th row: + +``` +db > insert 15 user15 person15@example.com +Need to implement searching an internal node +``` + +First, replace the code stub with a new function call. + +```diff + if (get_node_type(root_node) == NODE_LEAF) { + return leaf_node_find(table, root_page_num, key); + } else { +- printf("Need to implement searching an internal node\n"); +- exit(EXIT_FAILURE); ++ return internal_node_find(table, root_page_num, key); + } + } +``` + +This function will perform binary search to find the child that should contain the given key. Remember that the key to the right of each child pointer is the maximum key contained by that child. + +{% include image.html url="assets/images/btree6.png" description="three-level btree" %} + +So our binary search compares the key to find and the key to the right of the child pointer: + +```diff ++Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { ++ void* node = get_page(table->pager, page_num); ++ uint32_t num_keys = *internal_node_num_keys(node); ++ ++ /* Binary search to find index of child to search */ ++ uint32_t min_index = 0; ++ uint32_t max_index = num_keys; /* there is one more child than key */ ++ ++ while (min_index != max_index) { ++ uint32_t index = (min_index + max_index) / 2; ++ uint32_t key_to_right = *internal_node_key(node, index); ++ if (key_to_right >= key) { ++ max_index = index; ++ } else { ++ min_index = index + 1; ++ } ++ } +``` + +Also remember that the children of an internal node can be either leaf nodes or more internal nodes. After we find the correct child, call the appropriate search function on it: + +```diff ++ uint32_t child_num = *internal_node_child(node, min_index); ++ void* child = get_page(table->pager, child_num); ++ switch (get_node_type(child)) { ++ case NODE_LEAF: ++ return leaf_node_find(table, child_num, key); ++ case NODE_INTERNAL: ++ return internal_node_find(table, child_num, key); ++ } ++} +``` + +# Tests + +Now inserting a key into a multi-node btree no longer results in an error. And we can update our test: + +```diff + " - 12", + " - 13", + " - 14", +- "db > Need to implement searching an internal node", ++ "db > Executed.", ++ "db > ", + ]) + end +``` + +I also think it's time we revisit another test. The one that tries inserting 1400 rows. It still errors, but the error message is new. Right now, our tests don't handle it very well when the program crashes. If that happens, let's just use the output we've gotten so far: + +```diff + raw_output = nil + IO.popen("./db test.db", "r+") do |pipe| + commands.each do |command| +- pipe.puts command ++ begin ++ pipe.puts command ++ rescue Errno::EPIPE ++ break ++ end + end + + pipe.close_write +``` + +And that reveals that our 1400-row test outputs this error: + +```diff + end + script << ".exit" + result = run_script(script) +- expect(result[-2]).to eq('db > Error: Table full.') ++ expect(result.last(2)).to eq([ ++ "db > Executed.", ++ "db > Need to implement updating parent after split", ++ ]) + end +``` + +Looks like that's next on our to-do list! + diff --git a/db.c b/db.c index 02c82c1..d5d8ab2 100644 --- a/db.c +++ b/db.c @@ -372,6 +372,34 @@ Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { return cursor; } +Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { + void* node = get_page(table->pager, page_num); + uint32_t num_keys = *internal_node_num_keys(node); + + /* Binary search to find index of child to search */ + uint32_t min_index = 0; + uint32_t max_index = num_keys; /* there is one more child than key */ + + while (min_index != max_index) { + uint32_t index = (min_index + max_index) / 2; + uint32_t key_to_right = *internal_node_key(node, index); + if (key_to_right >= key) { + max_index = index; + } else { + min_index = index + 1; + } + } + + uint32_t child_num = *internal_node_child(node, min_index); + void* child = get_page(table->pager, child_num); + switch (get_node_type(child)) { + case NODE_LEAF: + return leaf_node_find(table, child_num, key); + case NODE_INTERNAL: + return internal_node_find(table, child_num, key); + } +} + /* Return the position of the given key. If the key is not present, return the position @@ -384,8 +412,7 @@ Cursor* table_find(Table* table, uint32_t key) { if (get_node_type(root_node) == NODE_LEAF) { return leaf_node_find(table, root_page_num, key); } else { - printf("Need to implement searching an internal node\n"); - exit(EXIT_FAILURE); + return internal_node_find(table, root_page_num, key); } } diff --git a/spec/main_spec.rb b/spec/main_spec.rb index 30eee90..e736f44 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -7,7 +7,11 @@ def run_script(commands) raw_output = nil IO.popen("./db test.db", "r+") do |pipe| commands.each do |command| - pipe.puts command + begin + pipe.puts command + rescue Errno::EPIPE + break + end end pipe.close_write @@ -59,7 +63,10 @@ def run_script(commands) end script << ".exit" result = run_script(script) - expect(result[-2]).to eq('db > Error: Table full.') + expect(result.last(2)).to eq([ + "db > Executed.", + "db > Need to implement updating parent after split", + ]) end it 'allows inserting strings that are the maximum length' do @@ -176,7 +183,8 @@ def run_script(commands) " - 12", " - 13", " - 14", - "db > Need to implement searching an internal node", + "db > Executed.", + "db > ", ]) end From 8e1773fefef6b094b9615c8448fdbd57e08b1dcf Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 11 Nov 2017 14:19:12 -0800 Subject: [PATCH 05/58] Code changes --- db.c | 56 ++++++++++++++++++++++++++++++----------------- spec/main_spec.rb | 30 ++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/db.c b/db.c index d5d8ab2..d7961ca 100644 --- a/db.c +++ b/db.c @@ -102,10 +102,8 @@ const uint32_t NODE_TYPE_SIZE = sizeof(uint8_t); const uint32_t NODE_TYPE_OFFSET = 0; const uint32_t IS_ROOT_SIZE = sizeof(uint8_t); const uint32_t IS_ROOT_OFFSET = NODE_TYPE_SIZE; -const uint32_t PARENT_POINTER_SIZE = sizeof(uint32_t); -const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE; const uint8_t COMMON_NODE_HEADER_SIZE = - NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE; + NODE_TYPE_SIZE + IS_ROOT_SIZE; /* * Internal Node Header Layout @@ -132,8 +130,12 @@ const uint32_t INTERNAL_NODE_CELL_SIZE = */ const uint32_t LEAF_NODE_NUM_CELLS_SIZE = sizeof(uint32_t); const uint32_t LEAF_NODE_NUM_CELLS_OFFSET = COMMON_NODE_HEADER_SIZE; -const uint32_t LEAF_NODE_HEADER_SIZE = - COMMON_NODE_HEADER_SIZE + LEAF_NODE_NUM_CELLS_SIZE; +const uint32_t LEAF_NODE_NEXT_LEAF_SIZE = sizeof(uint32_t); +const uint32_t LEAF_NODE_NEXT_LEAF_OFFSET = + LEAF_NODE_NUM_CELLS_OFFSET + LEAF_NODE_NUM_CELLS_SIZE; +const uint32_t LEAF_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE + + LEAF_NODE_NUM_CELLS_SIZE + + LEAF_NODE_NEXT_LEAF_SIZE; /* * Leaf Node Body Layout @@ -203,6 +205,10 @@ uint32_t* leaf_node_num_cells(void* node) { return node + LEAF_NODE_NUM_CELLS_OFFSET; } +uint32_t* leaf_node_next_leaf(void* node) { + return node + LEAF_NODE_NEXT_LEAF_OFFSET; +} + void* leaf_node_cell(void* node, uint32_t cell_num) { return node + LEAF_NODE_HEADER_SIZE + cell_num * LEAF_NODE_CELL_SIZE; } @@ -322,6 +328,7 @@ void initialize_leaf_node(void* node) { set_node_type(node, NODE_LEAF); set_node_root(node, false); *leaf_node_num_cells(node) = 0; + *leaf_node_next_leaf(node) = 0; // 0 represents no sibling } void initialize_internal_node(void* node) { @@ -330,19 +337,6 @@ void initialize_internal_node(void* node) { *internal_node_num_keys(node) = 0; } -Cursor* table_start(Table* table) { - Cursor* cursor = malloc(sizeof(Cursor)); - cursor->table = table; - cursor->page_num = table->root_page_num; - cursor->cell_num = 0; - - void* root_node = get_page(table->pager, table->root_page_num); - uint32_t num_cells = *leaf_node_num_cells(root_node); - cursor->end_of_table = (num_cells == 0); - - return cursor; -} - Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { void* node = get_page(table->pager, page_num); uint32_t num_cells = *leaf_node_num_cells(node); @@ -416,6 +410,16 @@ Cursor* table_find(Table* table, uint32_t key) { } } +Cursor* table_start(Table* table) { + Cursor* cursor = table_find(table, 0); + + void* node = get_page(table->pager, cursor->page_num); + uint32_t num_cells = *leaf_node_num_cells(node); + cursor->end_of_table = (num_cells == 0); + + return cursor; +} + void* cursor_value(Cursor* cursor) { uint32_t page_num = cursor->page_num; void* page = get_page(cursor->table->pager, page_num); @@ -428,7 +432,15 @@ void cursor_advance(Cursor* cursor) { cursor->cell_num += 1; if (cursor->cell_num >= (*leaf_node_num_cells(node))) { - cursor->end_of_table = true; + /* Advance to next leaf node */ + uint32_t next_page_num = *leaf_node_next_leaf(node); + if (next_page_num == 0) { + /* This was rightmost leaf */ + cursor->end_of_table = true; + } else { + cursor->page_num = next_page_num; + cursor->cell_num = 0; + } } } @@ -659,6 +671,8 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { uint32_t new_page_num = get_unused_page_num(cursor->table->pager); void* new_node = get_page(cursor->table->pager, new_page_num); initialize_leaf_node(new_node); + *leaf_node_next_leaf(new_node) = *leaf_node_next_leaf(old_node); + *leaf_node_next_leaf(old_node) = new_page_num; /* All existing keys plus new key should should be divided @@ -676,7 +690,9 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { void* destination = leaf_node_cell(destination_node, index_within_node); if (i == cursor->cell_num) { - serialize_row(value, destination); + serialize_row(value, + leaf_node_value(destination_node, index_within_node)); + *leaf_node_key(destination_node, index_within_node) = key; } else if (i > cursor->cell_num) { memcpy(destination, leaf_node_cell(old_node, i - 1), LEAF_NODE_CELL_SIZE); } else { diff --git a/spec/main_spec.rb b/spec/main_spec.rb index e736f44..7ce1c08 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -198,7 +198,7 @@ def run_script(commands) expect(result).to eq([ "db > Constants:", "ROW_SIZE: 293", - "COMMON_NODE_HEADER_SIZE: 6", + "COMMON_NODE_HEADER_SIZE: 2", "LEAF_NODE_HEADER_SIZE: 10", "LEAF_NODE_CELL_SIZE: 297", "LEAF_NODE_SPACE_FOR_CELLS: 4086", @@ -206,4 +206,32 @@ def run_script(commands) "db > ", ]) end + + it 'prints all rows in a multi-level tree' do + script = [] + (1..15).each do |i| + script << "insert #{i} user#{i} person#{i}@example.com" + end + script << "select" + script << ".exit" + result = run_script(script) + expect(result[15...result.length]).to eq([ + "db > (1, user1, person1@example.com)", + "(2, user2, person2@example.com)", + "(3, user3, person3@example.com)", + "(4, user4, person4@example.com)", + "(5, user5, person5@example.com)", + "(6, user6, person6@example.com)", + "(7, user7, person7@example.com)", + "(8, user8, person8@example.com)", + "(9, user9, person9@example.com)", + "(10, user10, person10@example.com)", + "(11, user11, person11@example.com)", + "(12, user12, person12@example.com)", + "(13, user13, person13@example.com)", + "(14, user14, person14@example.com)", + "(15, user15, person15@example.com)", + "Executed.", "db > ", + ]) + end end From c9d69ab697aea700e7c9673d4b4bd1e53fece165 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 11 Nov 2017 14:21:06 -0800 Subject: [PATCH 06/58] Part 12 article --- _parts/part12.md | 289 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 _parts/part12.md diff --git a/_parts/part12.md b/_parts/part12.md new file mode 100644 index 0000000..a678e20 --- /dev/null +++ b/_parts/part12.md @@ -0,0 +1,289 @@ +--- +title: Part 12 - Scanning a Multi-Level B-Tree +date: 2017-11-11 +--- + +We now support constructing a multi-level btree, but we've broken `select` statements in the process. Here's a test case that inserts 15 rows and then tries to print them. + +```diff ++ it 'prints all rows in a multi-level tree' do ++ script = [] ++ (1..15).each do |i| ++ script << "insert #{i} user#{i} person#{i}@example.com" ++ end ++ script << "select" ++ script << ".exit" ++ result = run_script(script) ++ ++ expect(result[15...result.length]).to eq([ ++ "db > (1, user1, person1@example.com)", ++ "(2, user2, person2@example.com)", ++ "(3, user3, person3@example.com)", ++ "(4, user4, person4@example.com)", ++ "(5, user5, person5@example.com)", ++ "(6, user6, person6@example.com)", ++ "(7, user7, person7@example.com)", ++ "(8, user8, person8@example.com)", ++ "(9, user9, person9@example.com)", ++ "(10, user10, person10@example.com)", ++ "(11, user11, person11@example.com)", ++ "(12, user12, person12@example.com)", ++ "(13, user13, person13@example.com)", ++ "(14, user14, person14@example.com)", ++ "(15, user15, person15@example.com)", ++ "Executed.", "db > ", ++ ]) ++ end +``` + +But when we run that test case right now, what actually happens is: + +``` +db > select +(2, user1, person1@example.com) +Executed. +``` + +That's weird. It's only printing one row, and that row looks corrupted (notice the id doesn't match the username). + +The weirdness is because `execute_select()` begins at the start of the table, and our current implementation of `table_start()` returns cell 0 of the root node. But the root of our tree is now an internal node which doesn't contain any rows. The data that was printed must have been left over from when the root node was a leaf. `execute_select()` should really return cell 0 of the leftmost leaf node. + +So get rid of the old implementation: + +```diff +-Cursor* table_start(Table* table) { +- Cursor* cursor = malloc(sizeof(Cursor)); +- cursor->table = table; +- cursor->page_num = table->root_page_num; +- cursor->cell_num = 0; +- +- void* root_node = get_page(table->pager, table->root_page_num); +- uint32_t num_cells = *leaf_node_num_cells(root_node); +- cursor->end_of_table = (num_cells == 0); +- +- return cursor; +-} +``` + +And add a new implementation that searches for key 0 (the minimum possible key). Even if key 0 does not exist in the table, this method will return the position of the lowest id (the start of the left-most leaf node). + +```diff ++Cursor* table_start(Table* table) { ++ Cursor* cursor = table_find(table, 0); ++ ++ void* node = get_page(table->pager, cursor->page_num); ++ uint32_t num_cells = *leaf_node_num_cells(node); ++ cursor->end_of_table = (num_cells == 0); ++ ++ return cursor; ++} +``` + +With those changes, it still only prints out one node's worth of rows: + +``` +db > select +(1, user1, person1@example.com) +(2, user2, person2@example.com) +(3, user3, person3@example.com) +(4, user4, person4@example.com) +(5, user5, person5@example.com) +(6, user6, person6@example.com) +(7, user7, person7@example.com) +Executed. +db > +``` + +With 15 entries, our btree consists of one internal node and two leaf nodes, which looks something like this: + +{% include image.html url="assets/images/btree3.png" description="structure of our btree" %} + +To scan the entire table, we need to jump to the second leaf node after we reach the end of the first. To do that, we're going to save a new field in the leaf node header called "next_leaf", which will hold the page number of the leaf's sibling node on the right. The rightmost leaf node will have a `next_leaf` value of 0 to denote no sibling (page 0 is reserved for the root node of the table anyway). + +Update the leaf node header format to include the new field: + +```diff + const uint32_t LEAF_NODE_NUM_CELLS_SIZE = sizeof(uint32_t); + const uint32_t LEAF_NODE_NUM_CELLS_OFFSET = COMMON_NODE_HEADER_SIZE; +-const uint32_t LEAF_NODE_HEADER_SIZE = +- COMMON_NODE_HEADER_SIZE + LEAF_NODE_NUM_CELLS_SIZE; ++const uint32_t LEAF_NODE_NEXT_LEAF_SIZE = sizeof(uint32_t); ++const uint32_t LEAF_NODE_NEXT_LEAF_OFFSET = ++ LEAF_NODE_NUM_CELLS_OFFSET + LEAF_NODE_NUM_CELLS_SIZE; ++const uint32_t LEAF_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE + ++ LEAF_NODE_NUM_CELLS_SIZE + ++ LEAF_NODE_NEXT_LEAF_SIZE; + + ``` + +Add a method to access the new field: +```diff ++uint32_t* leaf_node_next_leaf(void* node) { ++ return node + LEAF_NODE_NEXT_LEAF_OFFSET; ++} +``` + +Set `next_leaf` to 0 by default when initializing a new leaf node: + +```diff +@@ -322,6 +330,7 @@ void initialize_leaf_node(void* node) { + set_node_type(node, NODE_LEAF); + set_node_root(node, false); + *leaf_node_num_cells(node) = 0; ++ *leaf_node_next_leaf(node) = 0; // 0 represents no sibling + } +``` + +Whenever we split a leaf node, update the sibling pointers. The old leaf's sibling becomes the new leaf, and the new leaf's sibling becomes whatever used to be the old leaf's sibling. + +```diff +@@ -659,6 +671,8 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { + uint32_t new_page_num = get_unused_page_num(cursor->table->pager); + void* new_node = get_page(cursor->table->pager, new_page_num); + initialize_leaf_node(new_node); ++ *leaf_node_next_leaf(new_node) = *leaf_node_next_leaf(old_node); ++ *leaf_node_next_leaf(old_node) = new_page_num; +``` + +Adding a new field changes a few constants: +```diff + it 'prints constants' do + script = [ + ".constants", +@@ -199,9 +228,9 @@ describe 'database' do + "db > Constants:", + "ROW_SIZE: 293", + "COMMON_NODE_HEADER_SIZE: 6", +- "LEAF_NODE_HEADER_SIZE: 10", ++ "LEAF_NODE_HEADER_SIZE: 14", + "LEAF_NODE_CELL_SIZE: 297", +- "LEAF_NODE_SPACE_FOR_CELLS: 4086", ++ "LEAF_NODE_SPACE_FOR_CELLS: 4082", + "LEAF_NODE_MAX_CELLS: 13", + "db > ", + ]) +``` + +Now whenever we want to advance the cursor past the end of a leaf node, we can check if the leaf node has a sibling. If it does, jump to it. Otherwise, we're at the end of the table. + +```diff +@@ -428,7 +432,15 @@ void cursor_advance(Cursor* cursor) { + + cursor->cell_num += 1; + if (cursor->cell_num >= (*leaf_node_num_cells(node))) { +- cursor->end_of_table = true; ++ /* Advance to next leaf node */ ++ uint32_t next_page_num = *leaf_node_next_leaf(node); ++ if (next_page_num == 0) { ++ /* This was rightmost leaf */ ++ cursor->end_of_table = true; ++ } else { ++ cursor->page_num = next_page_num; ++ cursor->cell_num = 0; ++ } + } + } +``` + +After those changes, we actually print 15 rows... +``` +db > select +(1, user1, person1@example.com) +(2, user2, person2@example.com) +(3, user3, person3@example.com) +(4, user4, person4@example.com) +(5, user5, person5@example.com) +(6, user6, person6@example.com) +(7, user7, person7@example.com) +(8, user8, person8@example.com) +(9, user9, person9@example.com) +(10, user10, person10@example.com) +(11, user11, person11@example.com) +(12, user12, person12@example.com) +(13, user13, person13@example.com) +(1919251317, 14, on14@example.com) +(15, user15, person15@example.com) +Executed. +db > +``` + +...but one of them looks corrupted +``` +(1919251317, 14, on14@example.com) +``` + +After some debugging, I found out it's because of a bug in how we split leaf nodes: + +```diff +@@ -676,7 +690,9 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { + void* destination = leaf_node_cell(destination_node, index_within_node); + + if (i == cursor->cell_num) { +- serialize_row(value, destination); ++ serialize_row(value, ++ leaf_node_value(destination_node, index_within_node)); ++ *leaf_node_key(destination_node, index_within_node) = key; + } else if (i > cursor->cell_num) { + memcpy(destination, leaf_node_cell(old_node, i - 1), LEAF_NODE_CELL_SIZE); + } else { +``` + +Remember that each cell in a leaf node consists of first a key then a value: + +{% include image.html url="assets/images/leaf-node-format.png" description="Original leaf node format" %} + +We were writing the new row (value) into the start of the cell, where the key should go. That means part of the username was going into the section for id (hence the crazy large id). + +After fixing that bug, we finally print out the entire table as expected: + +``` +db > select +(1, user1, person1@example.com) +(2, user2, person2@example.com) +(3, user3, person3@example.com) +(4, user4, person4@example.com) +(5, user5, person5@example.com) +(6, user6, person6@example.com) +(7, user7, person7@example.com) +(8, user8, person8@example.com) +(9, user9, person9@example.com) +(10, user10, person10@example.com) +(11, user11, person11@example.com) +(12, user12, person12@example.com) +(13, user13, person13@example.com) +(14, user14, person14@example.com) +(15, user15, person15@example.com) +Executed. +db > +``` + +Whew! One bug after another, but we're making progress. Now that we've got the sibling pointer, I don't think we actually need a parent pointer. I added it preemptively, but we never actually used it. + +```diff + const uint32_t NODE_TYPE_OFFSET = 0; + const uint32_t IS_ROOT_SIZE = sizeof(uint8_t); + const uint32_t IS_ROOT_OFFSET = NODE_TYPE_SIZE; +-const uint32_t PARENT_POINTER_SIZE = sizeof(uint32_t); +-const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE; + const uint8_t COMMON_NODE_HEADER_SIZE = +- NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE; ++ NODE_TYPE_SIZE + IS_ROOT_SIZE; +``` + +```diff + expect(result).to eq([ + "db > Constants:", + "ROW_SIZE: 293", +- "COMMON_NODE_HEADER_SIZE: 6", +- "LEAF_NODE_HEADER_SIZE: 14", ++ "COMMON_NODE_HEADER_SIZE: 2", ++ "LEAF_NODE_HEADER_SIZE: 10", + "LEAF_NODE_CELL_SIZE: 297", +- "LEAF_NODE_SPACE_FOR_CELLS: 4082", ++ "LEAF_NODE_SPACE_FOR_CELLS: 4086", + "LEAF_NODE_MAX_CELLS: 13", + "db > ", + ]) +``` + +Until next time. From bf4d69664489faaad5ac0557436baa1fbf29b271 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Wed, 15 Nov 2017 21:52:35 -0800 Subject: [PATCH 07/58] Include 10 most recent parts in rss feed. --- feed.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feed.xml b/feed.xml index c867cd7..4f532ca 100644 --- a/feed.xml +++ b/feed.xml @@ -8,7 +8,9 @@ layout: none {{ site.description | xml_escape }} {{site.url}}{{site.baseurl}} - {% for part in site.parts limit:10 %} + {% assign limit = 10 %} + {% assign offset = (site.parts.size | minus: limit) %} + {% for part in site.parts offset:offset limit:limit %} {{ part.title | xml_escape }} {{ part.content | xml_escape }} From 1abefc65d6c7bb731b8676e3cd5e044f4d079670 Mon Sep 17 00:00:00 2001 From: Graywd Date: Thu, 23 Nov 2017 10:39:07 +0800 Subject: [PATCH 08/58] A slip of the pen In this expression 511^2 = 133,432,831, 511^3 is expected. --- _parts/part10.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part10.md b/_parts/part10.md index 8bf83ea..6b7e093 100644 --- a/_parts/part10.md +++ b/_parts/part10.md @@ -223,7 +223,7 @@ Notice our huge branching factor. Because each child pointer / key pair is so sm | 0 | 511^0 = 1 | 4 KB | | 1 | 511^1 = 512 | ~2 MB | | 2 | 511^2 = 261,121 | ~1 GB | -| 3 | 511^2 = 133,432,831 | ~550 GB | +| 3 | 511^3 = 133,432,831 | ~550 GB | In actuality, we can't store a full 4 KB of data per leaf node due to the overhead of the header, keys, and wasted space. But we can search through something like 500 GB of data by loading only 4 pages from disk. This is why the B-Tree is a useful data structure for databases. @@ -490,4 +490,4 @@ Need to implement searching an internal node Whoops! Who wrote that TODO message? :P -Next time we'll continue the epic B-tree saga by implementing search on a multi-level tree. \ No newline at end of file +Next time we'll continue the epic B-tree saga by implementing search on a multi-level tree. From 0ed745f3f683d03a76eedbee2ffa3afc59c6401c Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 25 Nov 2017 10:15:53 -0800 Subject: [PATCH 09/58] Add back parent pointer --- _parts/part12.md | 29 +---------------------------- db.c | 4 +++- spec/main_spec.rb | 6 +++--- 3 files changed, 7 insertions(+), 32 deletions(-) diff --git a/_parts/part12.md b/_parts/part12.md index a678e20..6d939f5 100644 --- a/_parts/part12.md +++ b/_parts/part12.md @@ -257,33 +257,6 @@ Executed. db > ``` -Whew! One bug after another, but we're making progress. Now that we've got the sibling pointer, I don't think we actually need a parent pointer. I added it preemptively, but we never actually used it. - -```diff - const uint32_t NODE_TYPE_OFFSET = 0; - const uint32_t IS_ROOT_SIZE = sizeof(uint8_t); - const uint32_t IS_ROOT_OFFSET = NODE_TYPE_SIZE; --const uint32_t PARENT_POINTER_SIZE = sizeof(uint32_t); --const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE; - const uint8_t COMMON_NODE_HEADER_SIZE = -- NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE; -+ NODE_TYPE_SIZE + IS_ROOT_SIZE; -``` - -```diff - expect(result).to eq([ - "db > Constants:", - "ROW_SIZE: 293", -- "COMMON_NODE_HEADER_SIZE: 6", -- "LEAF_NODE_HEADER_SIZE: 14", -+ "COMMON_NODE_HEADER_SIZE: 2", -+ "LEAF_NODE_HEADER_SIZE: 10", - "LEAF_NODE_CELL_SIZE: 297", -- "LEAF_NODE_SPACE_FOR_CELLS: 4082", -+ "LEAF_NODE_SPACE_FOR_CELLS: 4086", - "LEAF_NODE_MAX_CELLS: 13", - "db > ", - ]) -``` +Whew! One bug after another, but we're making progress. Until next time. diff --git a/db.c b/db.c index d7961ca..44ab198 100644 --- a/db.c +++ b/db.c @@ -102,8 +102,10 @@ const uint32_t NODE_TYPE_SIZE = sizeof(uint8_t); const uint32_t NODE_TYPE_OFFSET = 0; const uint32_t IS_ROOT_SIZE = sizeof(uint8_t); const uint32_t IS_ROOT_OFFSET = NODE_TYPE_SIZE; +const uint32_t PARENT_POINTER_SIZE = sizeof(uint32_t); +const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE; const uint8_t COMMON_NODE_HEADER_SIZE = - NODE_TYPE_SIZE + IS_ROOT_SIZE; + NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE; /* * Internal Node Header Layout diff --git a/spec/main_spec.rb b/spec/main_spec.rb index 7ce1c08..2f42dcc 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -198,10 +198,10 @@ def run_script(commands) expect(result).to eq([ "db > Constants:", "ROW_SIZE: 293", - "COMMON_NODE_HEADER_SIZE: 2", - "LEAF_NODE_HEADER_SIZE: 10", + "COMMON_NODE_HEADER_SIZE: 6", + "LEAF_NODE_HEADER_SIZE: 14", "LEAF_NODE_CELL_SIZE: 297", - "LEAF_NODE_SPACE_FOR_CELLS: 4086", + "LEAF_NODE_SPACE_FOR_CELLS: 4082", "LEAF_NODE_MAX_CELLS: 13", "db > ", ]) From 6592f148e3bca6fbb189523cdbb432aa6c09f655 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 25 Nov 2017 14:59:26 -0800 Subject: [PATCH 10/58] Update parent after creating new leaf node. --- db.c | 82 ++++++++++++++++++++++++++++++++++++++++++---- spec/main_spec.rb | 83 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 8 deletions(-) diff --git a/db.c b/db.c index 44ab198..37b4d8b 100644 --- a/db.c +++ b/db.c @@ -126,6 +126,8 @@ const uint32_t INTERNAL_NODE_KEY_SIZE = sizeof(uint32_t); const uint32_t INTERNAL_NODE_CHILD_SIZE = sizeof(uint32_t); const uint32_t INTERNAL_NODE_CELL_SIZE = INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE; +/* Keep this small for testing */ +const uint32_t INTERNAL_NODE_MAX_CELLS = 3; /* * Leaf Node Header Layout @@ -175,6 +177,8 @@ void set_node_root(void* node, bool is_root) { *((uint8_t*)(node + IS_ROOT_OFFSET)) = value; } +uint32_t* node_parent(void* node) { return node + PARENT_POINTER_OFFSET; } + uint32_t* internal_node_num_keys(void* node) { return node + INTERNAL_NODE_NUM_KEYS_OFFSET; } @@ -200,7 +204,7 @@ uint32_t* internal_node_child(void* node, uint32_t child_num) { } uint32_t* internal_node_key(void* node, uint32_t key_num) { - return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; + return (void*)internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; } uint32_t* leaf_node_num_cells(void* node) { @@ -368,11 +372,15 @@ Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { return cursor; } -Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { - void* node = get_page(table->pager, page_num); +uint32_t internal_node_find_child(void* node, uint32_t key) { + /* + Return the index of the child which should contain + the given key. + */ + uint32_t num_keys = *internal_node_num_keys(node); - /* Binary search to find index of child to search */ + /* Binary search */ uint32_t min_index = 0; uint32_t max_index = num_keys; /* there is one more child than key */ @@ -386,7 +394,14 @@ Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { } } - uint32_t child_num = *internal_node_child(node, min_index); + return min_index; +} + +Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { + void* node = get_page(table->pager, page_num); + + uint32_t child_index = internal_node_find_child(node, key); + uint32_t child_num = *internal_node_child(node, child_index); void* child = get_page(table->pager, child_num); switch (get_node_type(child)) { case NODE_LEAF: @@ -660,6 +675,52 @@ void create_new_root(Table* table, uint32_t right_child_page_num) { uint32_t left_child_max_key = get_node_max_key(left_child); *internal_node_key(root, 0) = left_child_max_key; *internal_node_right_child(root) = right_child_page_num; + *node_parent(left_child) = table->root_page_num; + *node_parent(right_child) = table->root_page_num; +} + +void internal_node_insert(Table* table, uint32_t parent_page_num, + uint32_t child_page_num) { + /* + Add a new child/key pair to parent that corresponds to child + */ + + void* parent = get_page(table->pager, parent_page_num); + void* child = get_page(table->pager, child_page_num); + uint32_t child_max_key = get_node_max_key(child); + uint32_t index = internal_node_find_child(parent, child_max_key); + + uint32_t original_num_keys = *internal_node_num_keys(parent); + *internal_node_num_keys(parent) = original_num_keys + 1; + + if (original_num_keys >= INTERNAL_NODE_MAX_CELLS) { + printf("Need to implement splitting internal node\n"); + exit(EXIT_FAILURE); + } + + uint32_t right_child_page_num = *internal_node_right_child(parent); + void* right_child = get_page(table->pager, right_child_page_num); + if (child_max_key > get_node_max_key(right_child)) { + /* Replace right child */ + *internal_node_child(parent, original_num_keys) = right_child_page_num; + *internal_node_key(parent, original_num_keys) = + get_node_max_key(right_child); + *internal_node_right_child(parent) = child_page_num; + } else { + /* Make room for the new cell */ + for (uint32_t i = original_num_keys; i > index; i--) { + void* destination = internal_node_cell(parent, i); + void* source = internal_node_cell(parent, i - 1); + memcpy(destination, source, INTERNAL_NODE_CELL_SIZE); + } + *internal_node_child(parent, index) = child_page_num; + *internal_node_key(parent, index) = child_max_key; + } +} + +void update_internal_node_key(void* node, uint32_t old_key, uint32_t new_key) { + uint32_t old_child_index = internal_node_find_child(node, old_key); + *internal_node_key(node, old_child_index) = new_key; } void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { @@ -670,9 +731,11 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { */ void* old_node = get_page(cursor->table->pager, cursor->page_num); + uint32_t old_max = get_node_max_key(old_node); uint32_t new_page_num = get_unused_page_num(cursor->table->pager); void* new_node = get_page(cursor->table->pager, new_page_num); initialize_leaf_node(new_node); + *node_parent(new_node) = *node_parent(old_node); *leaf_node_next_leaf(new_node) = *leaf_node_next_leaf(old_node); *leaf_node_next_leaf(old_node) = new_page_num; @@ -709,8 +772,13 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { if (is_node_root(old_node)) { return create_new_root(cursor->table, new_page_num); } else { - printf("Need to implement updating parent after split\n"); - exit(EXIT_FAILURE); + uint32_t parent_page_num = *node_parent(old_node); + uint32_t new_max = get_node_max_key(old_node); + void* parent = get_page(cursor->table->pager, parent_page_num); + + update_internal_node_key(parent, old_max, new_max); + internal_node_insert(cursor->table, parent_page_num, new_page_num); + return; } } diff --git a/spec/main_spec.rb b/spec/main_spec.rb index 2f42dcc..c09de74 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -65,7 +65,7 @@ def run_script(commands) result = run_script(script) expect(result.last(2)).to eq([ "db > Executed.", - "db > Need to implement updating parent after split", + "db > Need to implement splitting internal node", ]) end @@ -188,6 +188,87 @@ def run_script(commands) ]) end + it 'allows printing out the structure of a 4-leaf-node btree' do + script = [ + "insert 18 user18 person18@example.com", + "insert 7 user7 person7@example.com", + "insert 10 user10 person10@example.com", + "insert 29 user29 person29@example.com", + "insert 23 user23 person23@example.com", + "insert 4 user4 person4@example.com", + "insert 14 user14 person14@example.com", + "insert 30 user30 person30@example.com", + "insert 15 user15 person15@example.com", + "insert 26 user26 person26@example.com", + "insert 22 user22 person22@example.com", + "insert 19 user19 person19@example.com", + "insert 2 user2 person2@example.com", + "insert 1 user1 person1@example.com", + "insert 21 user21 person21@example.com", + "insert 11 user11 person11@example.com", + "insert 6 user6 person6@example.com", + "insert 20 user20 person20@example.com", + "insert 5 user5 person5@example.com", + "insert 8 user8 person8@example.com", + "insert 9 user9 person9@example.com", + "insert 3 user3 person3@example.com", + "insert 12 user12 person12@example.com", + "insert 27 user27 person27@example.com", + "insert 17 user17 person17@example.com", + "insert 16 user16 person16@example.com", + "insert 13 user13 person13@example.com", + "insert 24 user24 person24@example.com", + "insert 25 user25 person25@example.com", + "insert 28 user28 person28@example.com", + ".btree", + ".exit", + ] + result = run_script(script) + + expect(result[30...(result.length)]).to eq([ + "db > Tree:", + "- internal (size 3)", + " - leaf (size 7)", + " - 1", + " - 2", + " - 3", + " - 4", + " - 5", + " - 6", + " - 7", + " - key 7", + " - leaf (size 8)", + " - 8", + " - 9", + " - 10", + " - 11", + " - 12", + " - 13", + " - 14", + " - 15", + " - key 15", + " - leaf (size 7)", + " - 16", + " - 17", + " - 18", + " - 19", + " - 20", + " - 21", + " - 22", + " - key 22", + " - leaf (size 8)", + " - 23", + " - 24", + " - 25", + " - 26", + " - 27", + " - 28", + " - 29", + " - 30", + "db > ", + ]) + end + it 'prints constants' do script = [ ".constants", From 04be0e07ae791b818147e59c69ec7ccb2f2882d2 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sun, 26 Nov 2017 19:18:10 -0800 Subject: [PATCH 11/58] Part 13 article. --- _parts/part13.md | 302 +++++++++++++++++++++++ assets/images/updating-internal-node.png | Bin 0 -> 77477 bytes db.c | 1 + 3 files changed, 303 insertions(+) create mode 100644 _parts/part13.md create mode 100644 assets/images/updating-internal-node.png diff --git a/_parts/part13.md b/_parts/part13.md new file mode 100644 index 0000000..b7af456 --- /dev/null +++ b/_parts/part13.md @@ -0,0 +1,302 @@ +--- +title: Part 13 - Updating Parent Node After a Split +date: 2017-11-26 +--- + +For the next step on our epic b-tree implementation journey, we're going to handle fixing up the parent node after splitting a leaf. I'm going to use the following example as a reference: + +{% include image.html url="assets/images/updating-internal-node.png" description="Example of updating internal node" %} + +In this example, we add the key "3" to the tree. That causes the left leaf node to split. After the split we fix up the tree by doing the following: + +1. Update the first key in the parent to be the maximum key in the left child ("3") +2. Add a new child pointer / key pair after the updated key + - The new pointer points to the new child node + - The new key is the maximum key in the new child node ("5") + +So first things first, replace our stub code with two new function calls: `update_internal_node_key()` for step 1 and `internal_node_insert()` for step 2 + + +```diff +@@ -670,9 +725,11 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { + */ + + void* old_node = get_page(cursor->table->pager, cursor->page_num); ++ uint32_t old_max = get_node_max_key(old_node); + uint32_t new_page_num = get_unused_page_num(cursor->table->pager); + void* new_node = get_page(cursor->table->pager, new_page_num); + initialize_leaf_node(new_node); ++ *node_parent(new_node) = *node_parent(old_node); + *leaf_node_next_leaf(new_node) = *leaf_node_next_leaf(old_node); + *leaf_node_next_leaf(old_node) = new_page_num; + +@@ -709,8 +766,12 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { + if (is_node_root(old_node)) { + return create_new_root(cursor->table, new_page_num); + } else { +- printf("Need to implement updating parent after split\n"); +- exit(EXIT_FAILURE); ++ uint32_t parent_page_num = *node_parent(old_node); ++ uint32_t new_max = get_node_max_key(old_node); ++ void* parent = get_page(cursor->table->pager, parent_page_num); ++ ++ update_internal_node_key(parent, old_max, new_max); ++ internal_node_insert(cursor->table, parent_page_num, new_page_num); ++ return; + } + } +``` + +In order to get a reference to the parent, we need to start recording in each node a pointer to its parent node. + +```diff ++uint32_t* node_parent(void* node) { return node + PARENT_POINTER_OFFSET; } +``` +```diff +@@ -660,6 +675,48 @@ void create_new_root(Table* table, uint32_t right_child_page_num) { + uint32_t left_child_max_key = get_node_max_key(left_child); + *internal_node_key(root, 0) = left_child_max_key; + *internal_node_right_child(root) = right_child_page_num; ++ *node_parent(left_child) = table->root_page_num; ++ *node_parent(right_child) = table->root_page_num; + } +``` + +Now we need to find the affected cell in the parent node. The child doesn't know its own page number, so we can't look for that. But it does know its own maximum key, so we can search the parent for that key. + +```diff ++void update_internal_node_key(void* node, uint32_t old_key, uint32_t new_key) { ++ uint32_t old_child_index = internal_node_find_child(node, old_key); ++ *internal_node_key(node, old_child_index) = new_key; + } +``` + +Inside `internal_node_find_child()` we'll reuse some code we already have for finding a key in an internal node. Refactor `internal_node_find()` to use the new helper method. + +```diff +-Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { +- void* node = get_page(table->pager, page_num); ++uint32_t internal_node_find_child(void* node, uint32_t key) { ++ /* ++ Return the index of the child which should contain ++ the given key. ++ */ ++ + uint32_t num_keys = *internal_node_num_keys(node); + +- /* Binary search to find index of child to search */ ++ /* Binary search */ + uint32_t min_index = 0; + uint32_t max_index = num_keys; /* there is one more child than key */ + +@@ -386,7 +394,14 @@ Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { + } + } + +- uint32_t child_num = *internal_node_child(node, min_index); ++ return min_index; ++} ++ ++Cursor* internal_node_find(Table* table, uint32_t page_num, uint32_t key) { ++ void* node = get_page(table->pager, page_num); ++ ++ uint32_t child_index = internal_node_find_child(node, key); ++ uint32_t child_num = *internal_node_child(node, child_index); + void* child = get_page(table->pager, child_num); + switch (get_node_type(child)) { + case NODE_LEAF: +``` + +Now we get to the heart of this article, implementing `internal_node_insert()`. I'll explain it in pieces. + +```diff ++void internal_node_insert(Table* table, uint32_t parent_page_num, ++ uint32_t child_page_num) { ++ /* ++ Add a new child/key pair to parent that corresponds to child ++ */ ++ ++ void* parent = get_page(table->pager, parent_page_num); ++ void* child = get_page(table->pager, child_page_num); ++ uint32_t child_max_key = get_node_max_key(child); ++ uint32_t index = internal_node_find_child(parent, child_max_key); ++ ++ uint32_t original_num_keys = *internal_node_num_keys(parent); ++ *internal_node_num_keys(parent) = original_num_keys + 1; ++ ++ if (original_num_keys >= INTERNAL_NODE_MAX_CELLS) { ++ printf("Need to implement splitting internal node\n"); ++ exit(EXIT_FAILURE); ++ } +``` + +The index where the new cell (child/key pair) should be inserted depends on the maximum key in the new child. In the example we looked at, `child_max_key` would be 5 and `index` would be 1. + +If there's no room in the internal node for another cell, throw an error. We'll implement that later. + +Now let's look at the rest of the function: + +```diff ++ ++ uint32_t right_child_page_num = *internal_node_right_child(parent); ++ void* right_child = get_page(table->pager, right_child_page_num); ++ ++ if (child_max_key > get_node_max_key(right_child)) { ++ /* Replace right child */ ++ *internal_node_child(parent, original_num_keys) = right_child_page_num; ++ *internal_node_key(parent, original_num_keys) = ++ get_node_max_key(right_child); ++ *internal_node_right_child(parent) = child_page_num; ++ } else { ++ /* Make room for the new cell */ ++ for (uint32_t i = original_num_keys; i > index; i--) { ++ void* destination = internal_node_cell(parent, i); ++ void* source = internal_node_cell(parent, i - 1); ++ memcpy(destination, source, INTERNAL_NODE_CELL_SIZE); ++ } ++ *internal_node_child(parent, index) = child_page_num; ++ *internal_node_key(parent, index) = child_max_key; ++ } ++} +``` + +Because we store the rightmost child pointer separately from the rest of the child/key pairs, we have to handle things differently if the new child is going to become the rightmost child. + +In our example, we would get into the `else` block. First we make room for the new cell by shifting other cells one space to the right. (Although in our example there are 0 cells to shift) + +Next, we write the new child pointer and key into the cell determined by `index`. + +To reduce the size of testcases needed, I'm hardcoding `INTERNAL_NODE_MAX_CELLS` for now + +```diff +@@ -126,6 +126,8 @@ const uint32_t INTERNAL_NODE_KEY_SIZE = sizeof(uint32_t); + const uint32_t INTERNAL_NODE_CHILD_SIZE = sizeof(uint32_t); + const uint32_t INTERNAL_NODE_CELL_SIZE = + INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE; ++/* Keep this small for testing */ ++const uint32_t INTERNAL_NODE_MAX_CELLS = 3; +``` + +Speaking of tests, our large-dataset test gets past our old stub and gets to our new one: + +```diff +@@ -65,7 +65,7 @@ describe 'database' do + result = run_script(script) + expect(result.last(2)).to eq([ + "db > Executed.", +- "db > Need to implement updating parent after split", ++ "db > Need to implement splitting internal node", + ]) +``` + +Very satisfying, I know. + +I'll add another test that prints a four-node tree. Just so we test more cases than sequential ids, this test will add records in a pseudorandom order. + +```diff ++ it 'allows printing out the structure of a 4-leaf-node btree' do ++ script = [ ++ "insert 18 user18 person18@example.com", ++ "insert 7 user7 person7@example.com", ++ "insert 10 user10 person10@example.com", ++ "insert 29 user29 person29@example.com", ++ "insert 23 user23 person23@example.com", ++ "insert 4 user4 person4@example.com", ++ "insert 14 user14 person14@example.com", ++ "insert 30 user30 person30@example.com", ++ "insert 15 user15 person15@example.com", ++ "insert 26 user26 person26@example.com", ++ "insert 22 user22 person22@example.com", ++ "insert 19 user19 person19@example.com", ++ "insert 2 user2 person2@example.com", ++ "insert 1 user1 person1@example.com", ++ "insert 21 user21 person21@example.com", ++ "insert 11 user11 person11@example.com", ++ "insert 6 user6 person6@example.com", ++ "insert 20 user20 person20@example.com", ++ "insert 5 user5 person5@example.com", ++ "insert 8 user8 person8@example.com", ++ "insert 9 user9 person9@example.com", ++ "insert 3 user3 person3@example.com", ++ "insert 12 user12 person12@example.com", ++ "insert 27 user27 person27@example.com", ++ "insert 17 user17 person17@example.com", ++ "insert 16 user16 person16@example.com", ++ "insert 13 user13 person13@example.com", ++ "insert 24 user24 person24@example.com", ++ "insert 25 user25 person25@example.com", ++ "insert 28 user28 person28@example.com", ++ ".btree", ++ ".exit", ++ ] ++ result = run_script(script) +``` + +As-is, it will output this: + +``` +- internal (size 3) + - leaf (size 7) + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - key 1 + - leaf (size 8) + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - key 15 + - leaf (size 7) + - 16 + - 17 + - 18 + - 19 + - 20 + - 21 + - 22 + - key 22 + - leaf (size 8) + - 23 + - 24 + - 25 + - 26 + - 27 + - 28 + - 29 + - 30 +db > +``` + +Look carefully and you'll spot a bug: +``` + - 5 + - 6 + - 7 + - key 1 +``` + +The key there should be 7, not 1! + +After a bunch of debugging, I discovered this was due to some bad pointer arithmetic. + +```diff + uint32_t* internal_node_key(void* node, uint32_t key_num) { +- return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; ++ return (void*)internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE; + } +``` + +`INTERNAL_NODE_CHILD_SIZE` is 4. My here intention was to add 4 bytes to the result of `internal_node_cell()`, but since `internal_node_cell()` returns a `uint32_t*`, this it was actually adding `4 * sizeof(uint32_t)` bytes. I fixed it by casting to a `void*` before doing the arithmetic. + +NOTE! [Pointer arithmetic on void pointers is not part of the C standard and may not work with your compiler](https://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c/46238658#46238658). I may do an article in the future on portability, but I'm leaving my void pointer arithmetic for now. + +Alright. One more step toward a fully-operational btree implementation. The next step should be splitting internal nodes. Until then! diff --git a/assets/images/updating-internal-node.png b/assets/images/updating-internal-node.png new file mode 100644 index 0000000000000000000000000000000000000000..2e51fbfbc611ad44746226367bac777acebb7f2a GIT binary patch literal 77477 zcma&O1z40@`z{W{0E5&3(%}p#T@um^-GV3}2nYhwA<_yc(k)$zlqlUuigc-TgM^55 zH~iP^_&z=7ch33m>-skH&HK)ZC-3K3_u3)1G?fUi&|Ja5z#vpnme;|+fTA!kumoT@ z;2UeDhr$>b1Z387a<^3ENN$(*bIw;5IS62qS zNur5_QKYfA7@48u;$`pV?%gUiRnU{YYJ`ulZ7bq>-XIOvHZY*X*T3cY3i7ehZAQ}S z;@D+*MtnAXDp?TA@4Y0ahzRW+EQC@LZz$om6>DC$fXaOgEH4@SXn&e8?*9F-kPzrC z#gnF5&K4v z{%t58M5Z+-iLGNoCyl3L;i2$$DJJ|BB9mLD&&Ytmp)=D95hiZej&Z4=bb+FRt7X>u zew2&Cr2{;SH2c+q?{vWpbA2g;4y%(^viJ?SX09hJGm^Qykx5JKOWF11>9|Ztv+L@l zYcJ70e*0D5y5DK~eYvHgGC%qJLd)K}N5MjfM&`NBwVWd<6VjA7C7b49SaT_uI2hOG zN+3*#krzqGF4e8urb-FQKk(PTU6*~PUk}L(vux*>&%$n5d#9|BX5r&72AgRo$8C!k zdkA~}84(yC$iTkX!Pe#ZcKHf4L>vA=W{RgjH0;)q6we~Ae8gBVbE!^$F1)D+j@{91 z&$mvut!$h`pURSoKLYuY`X!1u{?x3?$k}BPhD#*Er`(UZ;PevjiUU;v&!zrLM->OR z@D_&2_CBJJ_+mk~TSjn^d2h*U6iGhxC2YwBTU2dEFKUnZw_O}WuQ1ADz@X{~e{T9- zXtK=Lkf1Xx4OM1^#PWmF)zx9?G-#42$T-vh!Kn*DL zQjrqVo2OT^TR}ATbG1i+RRB>7SqnFc?K^`qRLes9>Rmyo!rN;}f}fbb;$>lTmZ@Dz ziBHj$xwW4=^LAcy>7cIRDQRPCRzGvPmXsawMz>Urps=ju0B%soaE!(4lG^3m2!NV$j z>S``3B|bBA6Wdwa%j2M0gV5ugRhItc{{BRVK)>YL$MO&c>DMc`TIM{0>YY`z9j+Db zPS`SDRXtIW)db_o5qw2HgG=c+%?(#MJZD|G$S@)$vHYZtG^}=T5>9O}G%Lzk-a=|M zz38wYZ!qrEaJu?o@?j?MT~nHaNci)@Am0%*tuL8(n5)c$-;pT!=e=?X2n*$#Wz^##Lx?8Ej%dM7i)^ggAXj8VC+#9xXr`_%*|1XI zx9xYdzk(FsZ2!Cw7xW|Z=Ag8H@tLv%li$Jz_gnROR&X`UxW)I ztX-#+Ks|uFS+(>^!Icl6Q@&sNV6C>ElthV=9DGK`m@%QWBC8k6q17sOvIuZBa_rEu)BP~gn zN%i2>q^lu$A@r>d^B5uW1IY2{dgX7-6zs2cQ4{hLT4D+xHGd(WA~;_Wc113lx^NgM z^Dyc~AI50K7QcG^DkyF!t~X91?rprN>f^Y(?CW=J?taW2%DN%1ou_}R{`z*Ddbe4( zqALB8#1e9;u&k`Cw5)FDp@shr<5EO7Zago$JG*bT&8@5%u)-iN!u84Kv~62n1oIVre^1 zL^;j0Yq$f7_)YnW+RwU`kc@6wNzCQInfj+Dh`;_y0H226VNGmOE@7Sb&{q@k4 z;ISeVQ)+3bbFg#jwsrUgRTKXde^19yM6J}1h68jo!SGtf` zgjkfT^L^Xtu8-xe)hijJ>$D$kjy&e_wp$sKGv8dz^cwLh^?JPTwo>_v@F(rhpr6aX z90#O7%vv?`Z3%VW(6@3LcrzO9@YDV}pOKYE_ZD$3#b8u&-&ChW$C}81;1!`*s`VQ- zf;0kEe42bb0!1Rv9-{c21-tkRtOm>m9Bm$nb-89(7p^=NWDyh*Y#hfa53}6t4ZN~2~~-92NmeyFzXI4tZ# zSZ7Xu&gk%vP~VfVReBMPYN~2-hr}tojf)NZ>8Lv2J*Vx?L(#?U#JobQgk`BOlw{nj zE^HeaW9t@cZ95Yy!z+Dzn!7XKr&r7uKkwx4c+T6-_iRruoKAh|<{flwn&tHM_KmsJ zzC6Taz}&^s#5MCr`seuD%hJf=$)>(wz@^3AeQx{WR4z^KM_?KeOMqfP5ncmX?zMKJ z4|uD@?u4(eJ>z*!Lqb`4b6zF;N%r+JPf{^HXH_P>M|y`Wk9CB(QR|92ht@B3 zhSw6($2Or3p@vd6u0zJ_stbD$QXK-)oa`1g*ZTbyjw9cdhsC zb{f7O#IJCbtU(p)TQuAiWgnovfCeT883WXu*~&(x1O(N zE3x*-=wK?rQ*oE#%WBG|i0?Awq3p@?gqN)NVU!14R}6F;lFCGFw>K3I6qXb=6PUTJ zc}94Rb*Ij6cWPC#aIn0M6<3o?bTA3zCF8O++^Fyw+$qjU8{o7!upqa1tyiLFbUglj zIYOGaxL1F?s@r0?YPfZ{Ww?6us34z5kC&Hg(^%Zv{n3IajNH+8Z=3o!{*rS)KH$9=0EyHLf}zB>EItCobnE ztedQNXN_@#V{G5feSRaXsped#T*+N)JcHN!rMLJ8^ABH!Yet6F1}CfEQxwJz9c;Y* z*mN#4=btM!`7wPz{G{x~+?&O@YfZW5UZ=NOGZQ{L1g%CU(T}^tJ*%j|v`!lQI@{^E zW~XZXOO)bSLC$#j@lQv$rgcdN*ZQ4+k&ruV>}+9LM6Wp{gq*| z;V=;YARubsJ%6*Cd6G5R5%xM(O7dFDe)68@0_6wZJ`<)!DY|}YhJK3>{jv`jIJ7A- zn9ZpT7+-bfrZed=LPO8iS;*usjN_17uS?FJvXW6^9GpO+OJIJ=9!ldlsno5yV`{}S z+l?4cTxjgCkXVV{>^9f_jvu)7z~iV;3M1PRqcJ!zD5#2v*ms<;YX)o1HRG(0jhwN5 zcC+Nw+k*_fIi+K^kQequ@^WfIpi01VQZ{&ufkDB7{vW1_4)YcU24;q}uD+|jhPs%! zqdm`qhmK|zJRbH=;B5>H2@f&w(cZ%K0m8%H&f&3`ha}@)Z-{}<=&yMh5r4hnYAea8 zuW<_@=jdXA5aPMP!^bFf1%W_FxIDBJ(~(#F=jY)6Nisfib#)Ts<#l&==W!S0adffb z<-d9JCNG}=uYdqIc!T?~r-SPQ4{nFYOn(pZ?>O=nkIh}Iom{OQ9T4boADB5lag}6b zL{IdefBs&lg@^TjXL5M_&$hq@dC^aJ`FZ$w|1&oDsRa64v0K(27Iymb*7g<-kHH*L z{Cqe0B>wur|Muv=Q~vf-ga7_ilwb7sU;g&wpI=JwqIdAyj{X+cU*7_GNnMfP{g3RW zuIOw}g#mTYS<7qbg1->7Zh+>J!N0Em{TKZ?)HleeMGFH%217+&R@Vb_Egd)M>Yb55 zV#WuuE~G5Vq*Os~f=yFoW#HIXDdZL6$lF>!a-1}GU%@|J=;ruUL`Bi2@cdnt-y2f- zXl7Xw`PdRD%XQf}=8CYL*^i%lUDGs&hskHW7}9?kilpskyscYZ_5Q-`qDtR_i3yXz zfWZAQpa_ire3X!R149Mlz6}riUml>}c?5wWr(plX1CKxqRIn8)BI|#6h6r#X{@>?8 z%u$2EGVp{VQUBW-5Pt9$$^S0tKdbYzf*}GtScgQf{AaoUd4_?49saSAf1YF@{owP- zw-&DdzR7={fz4q34-5X!X13v?PIG>1({*}{mlxjS)sC;%W*Q8Mu1Rr8z1Gr96OFk% zOf-2Gl4tnq_dOukA#GuliLoqdsv)E-JC_NZdYb(=m+A8F7N(>rj~*|M=l(q!Asc3D zYU-1wdzCgSF^mdpk2r*7n_a+I2~1Wpb#VPEyJRktMx(})U!0OXpJ#dGEQ;?{zQ6c# zYb0X#3OUE4rD>C=B3C_$epkP~LUU3~nuy$ZQlZ_M`_`PSWY~DpH@jX-;S0g;^C+Dh zRh7Y9jisF)-b4!Sdq*bobdU@tAllk!@5%3dsUkh|ZDExdE^A}_grDJ(HX{Y>mA0e( zm&M?RGdM2~e-m>gt0~w1#gA0Soy*DE$5xG^E+Yn3T^5P=S@n#ZUk(+U%zk@jCIKY{ zii?E_O^Ajz7dKsSs3r2mAI|!kxUEm**h}yB0@NvZ`(CvJH>Hu2f0}bUb>t47Ir2B7 zffM`BK0DPIDKunjFL<<2ygeZreho|9eWRd>l9K!0jSQZAe9R6~8JM%(Pd*76|AmgI zp6A&36&O2%TGA`OhCk?ycDwf^aJT7;bp7Ee#U!FGi@^m(wK|vjkGS&qiI{3_5_#@3 zi;EkwJ=muqJKLwg41hbfO_I&fbZJRn9GQEbu2(Pp{FXJbEcWD=az`Y6j>;?63{$^X zzexoitk`@sQ}4EZFImtk(imkv#A{epFy*t`7aJe1;4=IOqjp0ds5p|4J}`)>+mS6@1g*#jMEL~{VauI7f>SoJ z;TDem;>YhbxKSD!TkBaAc7kWg{TR7A%QLF$@a!DVcfe3>zV)g=IiaLWnyEwL@ z7PJg6Feq0P5k8O=$z3Doyvudb|L70R!U9;fdU%_D<_MVhY`<)rd8>e5dwFp>ZotaL zeI*?Q7RDaa((eNaWR!GSROYWb;n02?F!k(oeE>T?@@mrb_ zQwzvD*4l1d-^0PQ1z}A8bF#KNV~PEw@NtLqMdq$Q6?MU8{jO%dZhovv!yw@w;R+t& zxBVi!RJUDUzgekDxFYFrZ?C#7j@4CIl04WObF}NLdyp_n-h@PBFBM{! zE|$cfOMLnCF*Cor6^6euv^|2B&C^p3hky#9*e^u{6MYGy|82&l2jY6g#@t%c=M`yU zPgHpt_H#Ao4r#>q?(f@9l)-0wzBEIMQqf6yqq+SX3i^?%P-P7JV$Pr>BU?9c} zi1v7-J?a8U6>&_sJZ-upGrwZII#QUU^;#-nvf1yCP04sb0;$ZHb%fj;eb!1lvLAd7 z{LZO!1(TLU23D%g$Hx-gEHdlE^f`z)k&I3A=aZnrsmH{EvJjH(D}SzNYtpf4g=LLa z*e)8xtgey=pR<9LT33dALc$|3P|7z@Mq2p(PfriJOfGgPZ+h*$8!9x^yV%Q^dE=!A!zNe|WMMgJWu!>|5eZXx7=r96_Sxnb7 zE|#S)4bVB}CRp9l_n>PDAbSMf&4&IlVhQ$NK}zt81e5o{+BZRzVkBJ`adK5Kfnop% z#iWr}EUa0!HhnJ*DR_*o=cpwnbX@Z_>W*W7b=RAU?vJ5@HRtkPKN~ncC(zE83nCh* zcAWhg@eb4f23!WFg5??*uHM`!cD`2r<=2&Vm!#b<($X##MSq%i6=TV>b1%>BHp)tk zc1;0FV~@RRYN%&*=9yy))fAhNk5?%C!lo-C9#wa-|5dNOJ zWSTY6hRv=exgWPSQK`0h@yPB&4<@EK+5~Rjkg)41v*=koUVc4_7av}ac$j>!)#tbR zn-xW}mAG#jr}MUs8N1Il`wsxW?>G&>+CW2x41KYYS0>Mn6&QTBWcgz`pZpq95p&;I z(>LM!Q>2|NeD)JJl0_UdjA_i*uHP=u=l?3xOaY`}M}p%>yWLLf*m=6y#7oYh3pe-d z1vax{kMJP=Zdriz$`QwvSR!Q#zSAV^lIUIDj4u1RMDW9eP*>;p~ zoQd!GosGIpsoJ^c_;hQ8xpIF%s0-*_;D*JGC-1NF8XH~g7yGJ=msy?fduyiU2L2DOBT)0~2_>>oQRLOA^zV)vX6VSD%`LsCpDIEFU>3$nrt zkb&k21H)-+2$>jL@b)2)MMADds#g+D(eI*Yo`&8nyvI`9aFBw=O~xMIvC{=xrx_N~ z)A0}iV>iWks_ZwWY6YI1dE^^6d?WxNI`k_0yqTjj;qTru!)n@lg<9Ay>1^vW@j#QW zuk~PVM796i(|cf)Z1N^|T#>486ZM|uQyvQuvFYjS_u(Eyzw0#$o&?N7JylpkKu~b0 zw1Z(cc(le^3CO`MJhaDN%wy{f0TrK2zvu!1zkqTg$DQsah6RQ{^s%)NCEbkrWf&vR3_R?6-5(zty!;)wMbO>8CRYKCit;<5l*le`Tstb0tU7 zh{P%oFwE+rYi8d_Umkzi=@XglEUcd40Fgicl)hc=5BHKG)30&LtUo`P9C#y(MJ?qc zs{5b0r)7OlR@fR0!)52LT^UandLGMe)GQELe|!5f zNTTD;AKPt4nD&yszF{4k=6i9xGum`PejQ$t1~S}IXM^}$2pL%)@1`E68@0SDO4_Djz`lnpYbPj+oA}buG?*WvzU{CdD>>(!O ztLndBX%j!?(!)Ifa5?d-=6zRmxWIi96?qJBf6rTmux#Md1Hgo$beiq|5Sr?B6zqKtcc3qY&_L0#wsvrYP7mJC;Iz_>orY32NLeI;PZBRE~m3; zNo)spuub3xG zDeB+`ep+<~Q1pJUq_`PKUueuO;QI3xx^nv;W?$}4-vOyhwJns~qBn`(xWoUF0>sBs zs+z>0I^Zo!Ao#rwr@N`soyfasarNkrn4m>A2I@(IYf?t4i9FoJ^*f0&Gm-iITN()&ZVb>8c;Yq>OA{~>Xi#jlxiJ7eZiX!yZS#;T5i2jyY`Ud`^X zNo3gz<+lu=>UjJ`k_{|x_kXp}4!kr`XHl|+Y)#Oi50$vq{mxP1gSiEbHgoDG}g{VK>j}0VGtrw3!fEcN- zF_pD|4Dh(I^?O-?;m-$l+aicvvqPd(f~taAFb*OeKEtr+NE=DK2h^{%4mAs9+_msL9_z030TV zfn+AQ8p?$Zjnp=d!~yX6^`iyJ-={hQyEz)1o3cS0H`(D&4;YexP{Kj3nH4%xbf0sx z>9P^D2qsx69^gRJWZwWF>GJ(0rE0aqblL;HyP!)DiN+L3w`vD&1;F(Jho5O81IBn+ z^`V%>aQ%9ZQgkt9>`W<*j3$K;(hA4nGI$_Zo({4@5px<~%9-%G`}K;jQZ;5H2V7Ke z4_3H0yoBBV1uk(n$PW&;SNZTE{ftgPr>bslRpsIB_h2&3RY1yiv@}{&f$;edzPwqL zfatvr41C0#0)$hqC~nlmcRH+SK@h6W_O-x3aAmfsY2|zX`G23$bN5GAh4rAF4ZWpo zvjG$YZx&F1x+Hwl$H#frMHAmO2q^1ah!# z`J};3W|Z=YxgR!sixIrSt4RO)wgS-Lorf!wkQo3G=KE?U+iU~ib@DAsZis*yiRKzy z%v91~)_}NTeFL%m`sv&ea|&nyOUXspQrSUy4hgOTEF?G(AMPgu0kf0(DUuBWlS&GV zjl2SELWhdJ2*8tkMz{tn-{s(@ONZQN9U+NxRI@fz>ikH~K;p0=SePk5Uj8#SOa_4o z^o1vr6=DsBQkPRvdI|Q-9!@lh!0`Wf`%jp_!bKFxUn5LSz}p8A5tLkDZ>Tu?IVMyv z;d4Jckj20nYcWWWJp`NU{|4dJ1qG6s!?I@(ZaRK+-e8!A6=t--;BWru=~?>h&I zX@Oj8_}?EvF;CHX$I-6dlo}OG`PL5W8VSk{4psN_!$pgp?`KUKA4o9)-@d^iaAiO6 zbI8|6W;9^d!CM2`55VHDWPKI_`?&)?>L?^Un?@@!Lc5I(HkJtV{M38_y9bV{hg;Ad z1^-oVL!c7qS6;|=ECqlmn8PCj|6dXXTjMC(TU7)?a0us+l5K7TldaLx^6CSdeR+Md zSt$Tc3&kWu%*ldR7zo68*wlflAGqEKz<`p1IzJZUMoN+*nDs`St1cDTPs>jyfZ>$M z!AISg91{_ApT}Hx8xL}T4w18C9-jn&&B%}ANHGSCAz(=un8e_fZPTyLHy@xCmz4!P zAQ}Vw9Qx*4>kaT#Zf15?3owVPib1wuKkvcE>`<=rk1;?9v{|8A@Dg4iwA1!>`~(nx zkln&tB*<1A*=8urBO1Jd`y#Rj4`c@wDtbaBhCdYioEam6H5XU~KOqY}Iaqu)pP11L z45-dORy-O3gfK;Q$LG1+37;_`u>{e)~=X_QL@_<}<1_IiTI6{oOnnbWIHit+90b zxdjYq99%}|BM&Q;h4W!T7|^eLdb8lt0;V8UR05Fj|7Fr7H@B=K>yO8Y*gfB78@Y7c zAE;36(*5pc(!_SyT|n91?ZD37W^37FKh5j>I_en%GT`Xb94MNVZvrvLFIRe7MZ(D6UKdz;;5q(`uQ5w;VmL^U?vh;_^g&!XW zq)l!+dmbg+7+XKRha>!m{hBQi{bC>Yrg9c3t6ZtJJCNBGoLre^EVRtv4{7Uwxh~Ar zblm_xs6b|dn4<&I34{yC;33Iezh0bOXs!%b7)?NG&Iwt%3Hr$`<^|^8^Ow!y(!bccQvI_f{T z4mDC}_C|H$83_C$F}Pj+!N^@BVJKf;$E`_3rs&kX2)=Wu5R^0M$*AP!!>(r8#7)~X z^XR*J7@5H8%=?{XV$zznBAUE_=!|?ylv{7Sf7V^)>Nc zL|wV@kC#4+j3}RKGv!#1L(fXB$pYZ1I5_2qIUTS~9>M~-DHP0E`AwPwD|)5D=Wr)h z%rAJzM&4naNKP%@CAhWgvU2UgGIFPvHg6%%c=mW2?kQvvew5`?VxOJnor}FKc^n~e zyEoZ1)gOeOkTRa-iX(; z6^iEn-diI>&hd$LvBV}&9rz%jo)@SxT7YfgeR-+#cROx;Hw(nW3kpEc8$dPN zqJq~hb#fcFt9&F*Jq<|r=8Q8@*w$a)_=Cppjw@G+O7-cfaP>0~6?OcL$4G|)`yI#{1vO5=2lc4)fH+Ou)wy~aqx z@EJQGnhK$f4T1Ch zs-R$H{GclI%K61+v;YfN685}Rl<&aZtg*7>>(7K+AA3EtjWqJS)8xkr`osx8`ciug zEV{e)s}B-smWPRJHuflCNX;n3fgDKVa8w#OZ?!=fe7-7pV(8kx2IB%Tra(pd@ZT+} z|4wo~=OEEc+fj7C?qOS~xxm?r@qw<;CBA2eqp>6w7R?_#vFRVSd~~-X zxQ;aDEDE%==C>R+8a&iy_chCV zHl5#LKTxr>?iM8DDr#th z%eQXL=4O6EOB}}?RnU53(R*(}XCM@*vP*IQjPY10InOOT->xl^zhlTjIO5!`oLcm@ zaZ^*R-Q~j~u4C8Cg7K3Ey0L1#@w*x{CKIYW*0XmejNeujSGEXCGq$g6jN8Z&I<*gLk)DZkr*O07w$dF9rqi+ZJuL}l(GeR=Zwpc;T2_YYjh?6~Bw^9@OW!WdNsN3~p zpvt={t~1D2IpH0>c7H=--Vl4bNz%@%>GGg=1{7b|ZfI67Q}mQF}mh-G5p?Q=lkSBB|D)&$_l+jD22x7 zj~UDlK>o*z{sClRg!b|l1&dc8$tMxSZTuUSfY(<->k$EVXFGz)@q)4!hfOBgb}v<3 z3R7Zk)isuGweoZzlij>Es2M7-di<6t)rwbBRH%I$WbBXYcGi8BFF$=MSNhnUO3S9g zgl|>as^+-bUMcW&NB`z=(`CN>lU1;qeh%C1vJ8(1qW4j7QfNRTSdS`LNA^|TbR{I{ zDPzME`@ml_A|)?>4Cp-5oE!4tx3t5D<7dP|ab6KAKP{+pdg3C8b>7>)KD!q`QfKD19JBcHC7k%|s_yaS ziT-|N9AtnzqufmZQ?8(43NVyX0Len)ukOc!q!4>miUt&hc%)7r;4Wv&SzZ*K352zE zNfi!*+GXNTw4!4b`h3_HJHI!$96j{BS?#r#O!}#=q7z%IXWrmECtTqNt5|Oo?!ihj z^7LRWZZ=*@&Ee;?iOsNW(j)hTDmhr`Rgo96&AUJu62AD^d2GE4rK%S$0a~Xbw|nmAjh;fJN^=WLfv4@_9|52SjFCsC*&jNE z2%wv4(&K$OUb)aD2C*+XZ>Z?rm}`D?{gqb6txHb!`KH_I%vz|}0{fjus{m zKQ^YE4OuX=YqMFz%Ae#L)Q%C|TG<`A$VDCtn`|#y3H(T)iVZp$8c4$NS2ItGWn!K+ zXnJ)=ZMmnO+HoZopT~wf-TVC0=6gv^64V+AZh@bl9>`A~ECUC7Wa#5b=kz-;WD!0) zk!&*wG>t2OEB&xBMTPJ7W2s`#5G18uNUOJULVAnY_807nz`Z5TuoF?R#`@K9t@S1NjR zDd_2}ZGF2=QZ6@dHN}3ZRG60b~ZgX^31$VoJ^s++do>>>31;vTvb8mfo&&(okMhQf!M~fQ~%gul!#q>$KB=W z=7rAkS@_im3d3p`vefx41r%XQ1d`li(E&9xpg+Df zdh{fOwc4j1t0Zx%!c<}kAK~XjSe^C2m4;r-ifKFDvG;Xjk%^r(L5w5RP-%C zkH`kasPdKX15b2;8)=hheXEpKHLgLGpIaic_q8(@|X74gm5o!_fV7~b}(tyr(z z{%CFUbaqq~Ry!e`7jzR9tQy9~Y0`N6M4F$;(z{ObrdHrIsR5O8dLe)6Bh|LUSs#s& zV&lwH8{cz{f%&oz>MJ5Mnjp)uC_V+#pou?SU$gAXfIMPZluwg@YqbW1r~-L(LD#qf z&fFL7#S=e#e&(|md{#7M;6{(Wm8j&=aX)HH#ev)`KT*o636vVsV zIe=<-(5mqqF!TXSnO{&?hXU0w!y+v~8q|TVaRhLg@EKC{_xA;8f7mz{yQX}sdM9>g zc&&YMw&u=9LSais*Laa?^;kvA%^k0`XuH``xr1wGD%tP4`|g$1$FaLt=@w~gbhqBT z+ifpI>kx2p_A)q($CgRY#P@MPNrOj|G=uy9{lcxr)M*kYLbwq^Yu!7{r zPr`a%0W^L{lL_^}qal(KkP%k9UhQN~kXwHK6+f}!DzVnC>b>40xmd;aHTs_c8u4VHq?t+aDW)$p^VLY((9r%*HPp>6$KIP~MD#z{5Z<#Cd(cwVlNudQ$X zt$dsM>DD{fkjy+rx`7zbSb*+gnPpPg&|MXFpBs_!!2S!e-{E4yl2n?BP@T_-XJX>R zqy`+)UwdX6O)dztKloI9iuZVmUoXXK|7+i+#M7|7kMHia+|Q}cd}XJB5y5ZoY`s3$) zcW;*KJJ-tg5#*1)Q(?TXVE+P6N=^b`0wx)-mn7J$qItBPhzQUy1D*;qiA@E7Yq2cR zJjjQqKL|Olb*{QBe$~>cmse>Ja>!!kj(rkzz`$b4Jf2=;8F=O{w~_F9bb3rrz|hD> zj1ctxV$dDnTQIPC)SMIh60j1T$Rtd3teC zsNFM~8Q~D2?l6bm^$}2#N*21~J#B>NK(9eM1@NC6({~=|0J_82hq-mM1v-vRm<2A^ zkgXg8;aHfkBeD_{Y`nxWXM)E4d78wrG`n7ASwF5$ow1y+plFca3UuUIm3*H}%DYqh zB+r-`<7(v?MwN&c|6*8YG^{0~8rDF!A~`h0!T|e*nUuO4v;}oZl`G+8j}BZ7s!S8y zGM_Evf}oRM-km+bauO&b<qO=otz^ zA5aQG!mRFFEb2VdyI4Nv*Qz~k7*%Y{S76}CVa(H3P3e96J#|@xZS$$&i z^O@NGPbLjRW0J?OVMzY0w~WA(Awa%lhyjIvTBM&n(1}bg>?25I2GCwO>e<+3!wk9Q zp@?Cnf!_U5Ii9YM6I^5~xR6}3Ey{IJwIq(%i?!dtK`E;zy@P_N@h=Sv4wf8H^6fer z>TZ)}NrAa={2#uIE^azOEn{t`i}F~-bMuZK)NA}o^Yy3B{e}zGQEm2B%2F#6@4Pd5 zA@vI!Oo8N%37Sgi{&Wc63Woza0x@ODGJ@@sW-8KxL~H=*f+5wUQrOj&8;UjFJ$@G0 zjq6eu$F3)3@ahpUW)IA#$|IxKS6V4*baB2XF52BY6JXD)p+SHf3{o{X*T7MAcLtdw z1fMdV3|MpQ^_vr*Y1;({hb+v4BKF8RA;>@J&9l$`4D{S|(6+8%x+2U9mc#$xtO6PgJrX&a?ty%sZ z_OR?=5K2Ou2kh0sBeNFyoQLRit`>a&%;-Pc4geXBGy^{ed}jt;1AvV4F&H%IbBys@ zaIg{r3GM-pRAD{`<5qwfaum0xkq6(R@dx~p?rHbgb)o8QO38t{jAvflb!|p{H#{5KW5)%*u2?YQPvTqu#u z_ZO+b=^@J&-~e}JtGsIUs1b}@EvnNjYB;#J3HkIG)+%F)KKBQdwqGm{17T*p1pF=m ze^GAuQo+W@&KGInOl1ueQaou+4C57c|^LcQMNLF<*8 z$J{28sL||ieoHNCGv$T$hz49p6wq88+Nju##iGzT{R)C7^&eOP0cn(t&v8Z~S=c_M z=+Po3|BgSEr!1h8_>bWo`ABgG0>Xr`$gj!j;tIQQfdisNFX21rnbq1YzdU!us+PBeR zEdMs!%2e&+v-GbJV3#uJxQ9U0SHuBH=F~KZQ=x+0&7+p#%0m8Mq)yet5Nui*aozEp zt2umxG&hv@OZJE{wMdcxjjcEB=Izt8_;{8Eul=34OHptPoumcgf=})ql{gr6%s^WZ z4{TedISU-7!(*DgHyhXT6_j;p&C{zI@XTLWZaMhbZ^zxJGMMqObg|@5cb7xH5j|3pnlXeG?0t=9WIM#0m za^3%^EAnw~K<$ZCG^0Xj>nL6&2y=rlI>2N6LNNaE$%l;eC(G&Xs+N|y*5`u&u+5>f z(MDwPbOUI-c?TGoT|)({K|*NZ%2@t@L}9#)HQxBNj^X^U>9U~$aHI(n_?Us(0HwCj z8u)V=pvgbY2Xx>8K$D!asRuY80Y^&@iib@Be5Mp$l^BGAZM*Dk(*t!L&4=+;z~*<4 z_x4T=9UJb@h&ZHq1XTb*>Yz*qvQ^4}k4(ECZcOLguXAIk4$=gxKLnd~*5XS4iH8a% z%laXcN`j)Jx}hE@lY-~7^$B+ekaTT01q1|KrJ`Myx^u=k0s)R;BuSEb`q5|ao6?C3 zV680z9Y*QKyP$bH&*3c^(da5*C3`colmsPl)z0XpOe{3=nskwsKbn|+IMn#mdj(if zY{LK=xIXto22ec@b~bu;yaYH|)%yHg=3pQqbi2CwO>O7H05~P~V1LnHOvW)B%u_gD zEDz1?wZrvEHmfw}KoDcz-{Cx^3T6R1)P=l$c9K6cKk*KP_s75-w>OhP2=w!l2Dai$ zlV%7ISgXxE13mFSxwKW*bbB5gwx&A%q7bqf`dZgA1V)Qwcn9v@jJ@=WkpQ<3Oybc* z)L)~;#nq$Sw{yeq_kIRS=c*?x&#_2rw-+rR>)FwFxKOS8gD`y9Ysh5cUm0xvF_dGi8&u}4-6I9~Rv#-MgZ z*!HawXL5)$e5}d0DFfAH(L*uatz7ho* z5ibah4?&t|mW!(TH&vU1wlXOhD~D|JZD@G@uA)@5c}L_5F0;Aq6d=ZH@i!a5Jp7x1 zivecZ5|I1s(>%!~$_I(KbcYDIzaKwsZxXpB z9f$_6Hm2*8-l+gi;Xl`siq4+`=kyIXdVoX)bG8JN!IhJm0bR2KX6EVyz!3<-Xfkw- zG@6sr&&ua};p2HY?OsV%Xjq*f_4Gt^5?KIz;@`S72~7%-PjV$s1|aRBu#vytBpwy9 zc)MJbvytmJg?mtTaG7Fqx?LKbsQkh8jLrZ&GS@;@jg+73Pr}GKDRRgYfnQdm{ZfrN zeFgBJ1Q&jYegJ++DL@b?GXQ;dwyh+I{}AA;kH!7z;%a%C>9HG$L_ofWaQ&ud4WK)5 zlmWP%f^Ko_CXMypbgg7yu78UqOyCIk2tjCFJGidaYzqw!qGe+L1unDX=;rGT)VVua z&(znB^34IBG$=Qje81K>FD%Y|dZANkb4OAz8$@CLitH;udJlmTG$S*1?Ui88&j{$h zV+x|^NLa%IK(kNk&DWPi}}?d@?5a1?5WU0_F8VAa=Zh^qtjeE`K4JY8ADS92OQP&E9 z-WX;FNT$w4okkXXsNfGR)+Sbe5s0{60N@`!Z5r-_02fFG`-*FS(5wvPs4?FE_E&0? zhqM>K&E0~X0Zh_4IavSj<@g#5 zg*@P*3<%s6kG?Uga`J$b4Ob5Li{WkUq&1@hm-D>5r@@&w>j-rrSlcA9*;0+`64u~m zCEI6*hCwvF)k+?g{k$36I9p72pZRwGH9)-gpn!(%#S!>c3!O2Jp~ChP+0~;$ZVCB! zJ|s|0uHFSa&`U8k5Vd!K=tOxf9m=SXts37dN_oL)IT2B=ED#;&z=In&x}%M2Wj)#M zn=|}X*?{_k;lGGSRi^_u0GbDzv&BG1-qRD6wrsQfl3*%pfIwm?)(E@+2^3cSoCSmr zu1~!PxH6tUntuzm1-|ts7PRO_NRs#- zJTK5MMG=o>DG}HHdjFw-HZ2A$IU8LOMnn_EqDf;=iIjG5{Ko8dcPK#SBi

axeB? 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 OCo8EWQ4V|Y=Dz?Yi|RlC literal 0 HcmV?d00001 diff --git a/db.c b/db.c index 37b4d8b..f467a00 100644 --- a/db.c +++ b/db.c @@ -700,6 +700,7 @@ void internal_node_insert(Table* table, uint32_t parent_page_num, uint32_t right_child_page_num = *internal_node_right_child(parent); void* right_child = get_page(table->pager, right_child_page_num); + if (child_max_key > get_node_max_key(right_child)) { /* Replace right child */ *internal_node_child(parent, original_num_keys) = right_child_page_num; From c403db2eff3566d5b2997710d27683402bfb8ae0 Mon Sep 17 00:00:00 2001 From: Wu Haotian Date: Sun, 10 Dec 2017 21:42:17 +0800 Subject: [PATCH 12/58] remove duplicated `should` wow, so much `should` --- _parts/part10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part10.md b/_parts/part10.md index 6b7e093..ebdbf25 100644 --- a/_parts/part10.md +++ b/_parts/part10.md @@ -68,7 +68,7 @@ Next, copy every cell into its new location: ```diff + /* -+ All existing keys plus new key should should be divided ++ All existing keys plus new key should be divided + evenly between old (left) and new (right) nodes. + Starting from the right, move each key to correct position. + */ From ee020df78731d9aaa9243dff536f1a2f3455802c Mon Sep 17 00:00:00 2001 From: Wu Haotian Date: Fri, 15 Dec 2017 23:33:15 +0800 Subject: [PATCH 13/58] repace `eq` with `match_array` in spec --- _parts/part10.md | 2 +- _parts/part11.md | 2 +- _parts/part12.md | 2 +- _parts/part13.md | 2 +- _parts/part4.md | 16 ++++++++-------- _parts/part5.md | 12 ++++++------ _parts/part8.md | 8 ++++---- _parts/part9.md | 2 +- spec/main_spec.rb | 26 +++++++++++++------------- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/_parts/part10.md b/_parts/part10.md index ebdbf25..b59c355 100644 --- a/_parts/part10.md +++ b/_parts/part10.md @@ -409,7 +409,7 @@ Here's a test case for the new printing functionality! + script << ".exit" + result = run_script(script) + -+ expect(result[14...(result.length)]).to eq([ ++ expect(result[14...(result.length)]).to match_array([ + "db > Tree:", + "- internal (size 1)", + " - leaf (size 7)", diff --git a/_parts/part11.md b/_parts/part11.md index 13a70b5..bef16f7 100644 --- a/_parts/part11.md +++ b/_parts/part11.md @@ -102,7 +102,7 @@ And that reveals that our 1400-row test outputs this error: script << ".exit" result = run_script(script) - expect(result[-2]).to eq('db > Error: Table full.') -+ expect(result.last(2)).to eq([ ++ expect(result.last(2)).to match_array([ + "db > Executed.", + "db > Need to implement updating parent after split", + ]) diff --git a/_parts/part12.md b/_parts/part12.md index 6d939f5..fedc6a7 100644 --- a/_parts/part12.md +++ b/_parts/part12.md @@ -15,7 +15,7 @@ We now support constructing a multi-level btree, but we've broken `select` state + script << ".exit" + result = run_script(script) + -+ expect(result[15...result.length]).to eq([ ++ expect(result[15...result.length]).to match_array([ + "db > (1, user1, person1@example.com)", + "(2, user2, person2@example.com)", + "(3, user3, person3@example.com)", diff --git a/_parts/part13.md b/_parts/part13.md index b7af456..6fa3720 100644 --- a/_parts/part13.md +++ b/_parts/part13.md @@ -182,7 +182,7 @@ Speaking of tests, our large-dataset test gets past our old stub and gets to our ```diff @@ -65,7 +65,7 @@ describe 'database' do result = run_script(script) - expect(result.last(2)).to eq([ + expect(result.last(2)).to match_array([ "db > Executed.", - "db > Need to implement updating parent after split", + "db > Need to implement splitting internal node", diff --git a/_parts/part4.md b/_parts/part4.md index 6ad6dc9..a64de62 100644 --- a/_parts/part4.md +++ b/_parts/part4.md @@ -32,7 +32,7 @@ describe 'database' do "select", ".exit", ]) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > (1, user1, person1@example.com)", "Executed.", @@ -85,7 +85,7 @@ it 'allows inserting strings that are the maximum length' do ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > (1, #{long_username}, #{long_email})", "Executed.", @@ -151,7 +151,7 @@ it 'prints error message if strings are too long' do ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > String is too long.", "db > Executed.", "db > ", @@ -263,7 +263,7 @@ it 'prints an error message if id is negative' do ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > ID must be positive.", "db > Executed.", "db > ", @@ -410,7 +410,7 @@ And we added tests: + "select", + ".exit", + ]) -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Executed.", + "db > (1, user1, person1@example.com)", + "Executed.", @@ -436,7 +436,7 @@ And we added tests: + ".exit", + ] + result = run_script(script) -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Executed.", + "db > (1, #{long_username}, #{long_email})", + "Executed.", @@ -453,7 +453,7 @@ And we added tests: + ".exit", + ] + result = run_script(script) -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > String is too long.", + "db > Executed.", + "db > ", @@ -467,7 +467,7 @@ And we added tests: + ".exit", + ] + result = run_script(script) -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > ID must be positive.", + "db > Executed.", + "db > ", diff --git a/_parts/part5.md b/_parts/part5.md index 8fe301e..83bcb59 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -13,7 +13,7 @@ it 'keeps data after closing connection' do "insert 1 user1 person1@example.com", ".exit", ]) - expect(result1).to eq([ + expect(result1).to match_array([ "db > Executed.", "db > ", ]) @@ -21,7 +21,7 @@ it 'keeps data after closing connection' do "select", ".exit", ]) - expect(result2).to eq([ + expect(result2).to match_array([ "db > (1, user1, person1@example.com)", "Executed.", "db > ", @@ -533,7 +533,7 @@ index 21561ce..bc0180a 100644 + "insert 1 user1 person1@example.com", + ".exit", + ]) -+ expect(result1).to eq([ ++ expect(result1).to match_array([ + "db > Executed.", + "db > ", + ]) @@ -542,7 +542,7 @@ index 21561ce..bc0180a 100644 + "select", + ".exit", + ]) -+ expect(result2).to eq([ ++ expect(result2).to match_array([ + "db > (1, user1, person1@example.com)", + "Executed.", + "db > ", @@ -577,7 +577,7 @@ And the diff to our tests: + "insert 1 user1 person1@example.com", + ".exit", + ]) -+ expect(result1).to eq([ ++ expect(result1).to match_array([ + "db > Executed.", + "db > ", + ]) @@ -586,7 +586,7 @@ And the diff to our tests: + "select", + ".exit", + ]) -+ expect(result2).to eq([ ++ expect(result2).to match_array([ + "db > (1, user1, person1@example.com)", + "Executed.", + "db > ", diff --git a/_parts/part8.md b/_parts/part8.md index 759d882..48b51a1 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -417,7 +417,7 @@ I'm also adding a test so we get alerted when those constants change: + ] + result = run_script(script) + -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Constants:", + "ROW_SIZE: 293", + "COMMON_NODE_HEADER_SIZE: 6", @@ -477,7 +477,7 @@ And a test + script << ".exit" + result = run_script(script) + -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Executed.", + "db > Executed.", + "db > Executed.", @@ -829,7 +829,7 @@ And the specs: + script << ".exit" + result = run_script(script) + -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Executed.", + "db > Executed.", + "db > Executed.", @@ -849,7 +849,7 @@ And the specs: + ] + result = run_script(script) + -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Constants:", + "ROW_SIZE: 293", + "COMMON_NODE_HEADER_SIZE: 6", diff --git a/_parts/part9.md b/_parts/part9.md index 8d1713d..cf1be57 100644 --- a/_parts/part9.md +++ b/_parts/part9.md @@ -184,7 +184,7 @@ And we can add a new test for duplicate keys: + ".exit", + ] + result = run_script(script) -+ expect(result).to eq([ ++ expect(result).to match_array([ + "db > Executed.", + "db > Error: Duplicate key.", + "db > (1, user1, person1@example.com)", diff --git a/spec/main_spec.rb b/spec/main_spec.rb index c09de74..4dbb826 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -28,7 +28,7 @@ def run_script(commands) "select", ".exit", ]) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > (1, user1, person1@example.com)", "Executed.", @@ -41,7 +41,7 @@ def run_script(commands) "insert 1 user1 person1@example.com", ".exit", ]) - expect(result1).to eq([ + expect(result1).to match_array([ "db > Executed.", "db > ", ]) @@ -50,7 +50,7 @@ def run_script(commands) "select", ".exit", ]) - expect(result2).to eq([ + expect(result2).to match_array([ "db > (1, user1, person1@example.com)", "Executed.", "db > ", @@ -63,7 +63,7 @@ def run_script(commands) end script << ".exit" result = run_script(script) - expect(result.last(2)).to eq([ + expect(result.last(2)).to match_array([ "db > Executed.", "db > Need to implement splitting internal node", ]) @@ -78,7 +78,7 @@ def run_script(commands) ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > (1, #{long_username}, #{long_email})", "Executed.", @@ -95,7 +95,7 @@ def run_script(commands) ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > String is too long.", "db > Executed.", "db > ", @@ -109,7 +109,7 @@ def run_script(commands) ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > ID must be positive.", "db > Executed.", "db > ", @@ -124,7 +124,7 @@ def run_script(commands) ".exit", ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > Error: Duplicate key.", "db > (1, user1, person1@example.com)", @@ -141,7 +141,7 @@ def run_script(commands) script << ".exit" result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > Executed.", "db > Executed.", "db > Executed.", @@ -163,7 +163,7 @@ def run_script(commands) script << ".exit" result = run_script(script) - expect(result[14...(result.length)]).to eq([ + expect(result[14...(result.length)]).to match_array([ "db > Tree:", "- internal (size 1)", " - leaf (size 7)", @@ -225,7 +225,7 @@ def run_script(commands) ] result = run_script(script) - expect(result[30...(result.length)]).to eq([ + expect(result[30...(result.length)]).to match_array([ "db > Tree:", "- internal (size 3)", " - leaf (size 7)", @@ -276,7 +276,7 @@ def run_script(commands) ] result = run_script(script) - expect(result).to eq([ + expect(result).to match_array([ "db > Constants:", "ROW_SIZE: 293", "COMMON_NODE_HEADER_SIZE: 6", @@ -296,7 +296,7 @@ def run_script(commands) script << "select" script << ".exit" result = run_script(script) - expect(result[15...result.length]).to eq([ + expect(result[15...result.length]).to match_array([ "db > (1, user1, person1@example.com)", "(2, user2, person2@example.com)", "(3, user3, person3@example.com)", From dea79bd988423a35fa171fb3f3fc81d9754feeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bournhonesque?= Date: Thu, 22 Mar 2018 16:10:31 +0100 Subject: [PATCH 14/58] Fix typo --- _parts/part7.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part7.md b/_parts/part7.md index 8635fc0..86c526e 100644 --- a/_parts/part7.md +++ b/_parts/part7.md @@ -66,7 +66,7 @@ Let's say that the capacity of a leaf node is two key/value pairs. When we inser The internal node has 1 key and 2 pointers to child nodes. If we want to look up a key that is less than or equal to 5, we look in the left child. If we want to look up a key greater than 5, we look in the right child. -Now let's insert the key "2". First we look up which leaf node it would be in if it was present, and we arrive at the left leaf node. The node is full, so we split the leaf node and create create a new entry in the parent node. +Now let's insert the key "2". First we look up which leaf node it would be in if it was present, and we arrive at the left leaf node. The node is full, so we split the leaf node and create a new entry in the parent node. {% include image.html url="assets/images/btree4.png" description="four-node btree" %} From e9ba556d972d902f3ac4759fa62995dedf22f31a Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Thu, 22 Mar 2018 22:19:50 -0700 Subject: [PATCH 15/58] Update nokogiri --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b0fcab3..b21668f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -173,14 +173,14 @@ GEM rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9.7) mercenary (0.3.6) - mini_portile2 (2.2.0) + mini_portile2 (2.3.0) minima (2.1.1) jekyll (~> 3.3) minitest (5.10.3) multipart-post (2.0.0) net-dns (0.8.0) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) octokit (4.7.0) sawyer (~> 0.8.0, >= 0.5.3) pathutil (0.14.0) @@ -230,4 +230,4 @@ DEPENDENCIES rspec BUNDLED WITH - 1.15.3 + 1.16.1 From 01c077c575324a6c65296ffde11d0004b4f46e8e Mon Sep 17 00:00:00 2001 From: sairoutine Date: Tue, 17 Apr 2018 18:28:44 +0900 Subject: [PATCH 16/58] fix uninitialized root_page_num --- db.c | 1 + 1 file changed, 1 insertion(+) diff --git a/db.c b/db.c index f467a00..bcee4dd 100644 --- a/db.c +++ b/db.c @@ -498,6 +498,7 @@ Table* db_open(const char* filename) { Table* table = malloc(sizeof(Table)); table->pager = pager; + table->root_page_num = 0; if (pager->num_pages == 0) { // New database file. Initialize page 0 as leaf node. From 3d5433325c4d5020a1051444fa68fee65c50c26f Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sat, 28 Apr 2018 22:34:28 -0700 Subject: [PATCH 17/58] Update copy --- _parts/part13.md | 2 +- _parts/part5.md | 6 ++++-- _parts/part7.md | 3 ++- _parts/part8.md | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/_parts/part13.md b/_parts/part13.md index 6fa3720..6957bb5 100644 --- a/_parts/part13.md +++ b/_parts/part13.md @@ -295,7 +295,7 @@ After a bunch of debugging, I discovered this was due to some bad pointer arithm } ``` -`INTERNAL_NODE_CHILD_SIZE` is 4. My here intention was to add 4 bytes to the result of `internal_node_cell()`, but since `internal_node_cell()` returns a `uint32_t*`, this it was actually adding `4 * sizeof(uint32_t)` bytes. I fixed it by casting to a `void*` before doing the arithmetic. +`INTERNAL_NODE_CHILD_SIZE` is 4. My intention here was to add 4 bytes to the result of `internal_node_cell()`, but since `internal_node_cell()` returns a `uint32_t*`, this it was actually adding `4 * sizeof(uint32_t)` bytes. I fixed it by casting to a `void*` before doing the arithmetic. NOTE! [Pointer arithmetic on void pointers is not part of the C standard and may not work with your compiler](https://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c/46238658#46238658). I may do an article in the future on portability, but I'm leaving my void pointer arithmetic for now. diff --git a/_parts/part5.md b/_parts/part5.md index 83bcb59..e4a63ea 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -122,7 +122,7 @@ Following our new abstraction, we move the logic for fetching a page into its ow } ``` -The `get_page()` method has the logic for handling a cache miss. We assume pages are saved one after the other in the database file: Page 0 at offset 0, page 1 at offset 4096, page 2 at offset 8192, etc. If the requested page lies outside the bounds of the file, we know it should be blank, so we just allocate some memory return it. The page will be added to the file when we flush the cache to disk later. +The `get_page()` method has the logic for handling a cache miss. We assume pages are saved one after the other in the database file: Page 0 at offset 0, page 1 at offset 4096, page 2 at offset 8192, etc. If the requested page lies outside the bounds of the file, we know it should be blank, so we just allocate some memory and return it. The page will be added to the file when we flush the cache to disk later. ```diff @@ -290,7 +290,9 @@ The next 256 bytes store the email in the same way. Here we can see some random ## Conclusion -Alright! We've got persistence. It's not the greatest. For example if you kill the program without typing `.exit`, you lose your changes. Additionally, we're writing all pages back to disk, even pages that haven't changed since we read them from disk. These are issues we can address later. The next thing I think we should work on is implementing the B-tree. +Alright! We've got persistence. It's not the greatest. For example if you kill the program without typing `.exit`, you lose your changes. Additionally, we're writing all pages back to disk, even pages that haven't changed since we read them from disk. These are issues we can address later. + +Next time we'll introduce cursors, which should make it easier to implement the B-tree. Until then! diff --git a/_parts/part7.md b/_parts/part7.md index 86c526e..e2478cd 100644 --- a/_parts/part7.md +++ b/_parts/part7.md @@ -8,7 +8,7 @@ The B-Tree is the data structure SQLite uses to represent both tables and indexe Why is a tree a good data structure for a database? - Searching for a particular value is fast (logarithmic time) -- Inserting / deleting a value is fast (constant-ish time to rebalance) +- Inserting / deleting a value you've already found is fast (constant-ish time to rebalance) - Traversing a range of values is fast (unlike a hash map) A B-Tree is different from a binary tree (the "B" probably stands for the inventor's name, but could also stand for "balanced"). Here's an example B-Tree: @@ -51,6 +51,7 @@ Let's work through an example to see how a B-tree grows as you insert elements i - up to 3 children per internal node - up to 2 keys per internal node - at least 2 children per internal node +- at least 1 key per internal node An empty B-tree has a single node: the root node. The root node starts as a leaf node with zero key/value pairs: diff --git a/_parts/part8.md b/_parts/part8.md index 48b51a1..a7e1419 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -9,7 +9,7 @@ We're changing the format of our table from an unsorted array of rows to a B-Tre With the current format, each page stores only rows (no metadata) so it is pretty space efficient. Insertion is also fast because we just append to the end. However, finding a particular row can only be done by scanning the entire table. And if we want to delete a row, we have to fill in the hole by moving every row that comes after it. -If we stored the table as an array, but kept rows sorted by id, we could use binary search to find a particular id. However, insertion would have the same problem as deletion where we have to move a lot of rows to make space. +If we stored the table as an array, but kept rows sorted by id, we could use binary search to find a particular id. However, insertion would be slow because we would have to move a lot of rows to make space. Instead, we're going with a tree structure. Each node in the tree can contain a variable number of rows, so we have to store some information in each node to keep track of how many rows it contains. Plus there is the storage overhead of all the internal nodes which don't store any rows. In exchange for a larger database file, we get fast insertion, deletion and lookup. From c64a936385dd68249abe9ac04852da62da0f76d0 Mon Sep 17 00:00:00 2001 From: tinyboy186 Date: Tue, 7 Aug 2018 14:14:08 +0700 Subject: [PATCH 18/58] Fix bug EXECUTE_DUPLICATE_KEY --- db.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db.c b/db.c index bcee4dd..193c3a4 100644 --- a/db.c +++ b/db.c @@ -808,13 +808,13 @@ void leaf_node_insert(Cursor* cursor, uint32_t key, Row* value) { } ExecuteResult execute_insert(Statement* statement, Table* table) { - void* node = get_page(table->pager, table->root_page_num); - uint32_t num_cells = (*leaf_node_num_cells(node)); - Row* row_to_insert = &(statement->row_to_insert); uint32_t key_to_insert = row_to_insert->id; Cursor* cursor = table_find(table, key_to_insert); + void *node = get_page(table->pager, cursor->page_num); + uint32_t num_cells = *leaf_node_num_cells(node); + if (cursor->cell_num < num_cells) { uint32_t key_at_index = *leaf_node_key(node, cursor->cell_num); if (key_at_index == key_to_insert) { From 65e833d66327bbf42de02a280d7527a0533403a6 Mon Sep 17 00:00:00 2001 From: tinyboy186 Date: Tue, 7 Aug 2018 16:20:45 +0700 Subject: [PATCH 19/58] Fix bug not initializing cursor->end_of_table --- db.c | 1 + 1 file changed, 1 insertion(+) diff --git a/db.c b/db.c index 193c3a4..7ae1eff 100644 --- a/db.c +++ b/db.c @@ -350,6 +350,7 @@ Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { Cursor* cursor = malloc(sizeof(Cursor)); cursor->table = table; cursor->page_num = page_num; + cursor->end_of_table = false; // Binary search uint32_t min_index = 0; From 7d81cd052bcb999ea57c9dd1fb1badf2b639ed07 Mon Sep 17 00:00:00 2001 From: tinyboy186 Date: Thu, 9 Aug 2018 12:58:31 +0700 Subject: [PATCH 20/58] Fix indentation --- db.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db.c b/db.c index 7ae1eff..61bc8a5 100644 --- a/db.c +++ b/db.c @@ -350,7 +350,7 @@ Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { Cursor* cursor = malloc(sizeof(Cursor)); cursor->table = table; cursor->page_num = page_num; - cursor->end_of_table = false; + cursor->end_of_table = false; // Binary search uint32_t min_index = 0; From 8c0429250a53e34ad804799ac244a90a72000e23 Mon Sep 17 00:00:00 2001 From: Arun Nanduri Date: Sun, 30 Sep 2018 16:48:22 -0400 Subject: [PATCH 21/58] Delete repeated word --- _parts/part8.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part8.md b/_parts/part8.md index a7e1419..349b1b7 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -298,7 +298,7 @@ A cursor represents a position in the table. When our table was a simple array o ## Insertion Into a Leaf Node -In this article we're only going to implement enough to get get a single-node tree. Recall from last article that a tree starts out as an empty leaf node: +In this article we're only going to implement enough to get a single-node tree. Recall from last article that a tree starts out as an empty leaf node: {% include image.html url="assets/images/btree1.png" description="empty btree" %} From 3d2562feaffb9723af79ed46865c200893760ec1 Mon Sep 17 00:00:00 2001 From: Arun Nanduri Date: Sun, 30 Sep 2018 16:52:37 -0400 Subject: [PATCH 22/58] Show edit to function before the complete diff --- _parts/part5.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/_parts/part5.md b/_parts/part5.md index e4a63ea..9659539 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -241,7 +241,7 @@ In our current design, the length of the file encodes how many rows are in the d +} ``` -Lastly, we need to accept the filename as a command-line argument: +Lastly, we need to accept the filename as a command-line argument. Don't forget to also add the extra argument to `do_meta_command`: ```diff int main(int argc, char* argv[]) { @@ -254,6 +254,14 @@ Lastly, we need to accept the filename as a command-line argument: + char* filename = argv[1]; + Table* table = db_open(filename); + + InputBuffer* input_buffer = new_input_buffer(); + while (true) { + print_prompt(); + read_input(input_buffer); + + if (input_buffer->buffer[0] == '.') { +- switch (do_meta_command(input_buffer)) { ++ switch (do_meta_command(input_buffer, table)) { ``` With these changes, we're able to close then reopen the database, and our records are still there! From 7b1a30e987c34a074f24ef6768ab1c2e3443c101 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sun, 30 Sep 2018 14:19:52 -0700 Subject: [PATCH 23/58] bundle update --- Gemfile.lock | 259 ++++++++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 114 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b21668f..f385382 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.8) + activesupport (4.2.10) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -11,200 +11,231 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.12.2) + coffee-script-source (1.11.1) colorator (1.1.0) + commonmarker (0.17.13) + ruby-enum (~> 0.5) + concurrent-ruby (1.0.5) diff-lcs (1.3) - ethon (0.10.1) + dnsruby (1.61.2) + addressable (~> 2.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + ethon (0.11.0) ffi (>= 1.3.0) + eventmachine (1.2.7) execjs (2.7.0) - faraday (0.13.1) + faraday (0.15.3) multipart-post (>= 1.2, < 3) - ffi (1.9.18) + ffi (1.9.25) forwardable-extended (2.6.0) gemoji (3.0.0) - github-pages (157) - activesupport (= 4.2.8) - github-pages-health-check (= 1.3.5) - jekyll (= 3.5.2) - jekyll-avatar (= 0.4.2) - jekyll-coffeescript (= 1.0.1) + github-pages (192) + activesupport (= 4.2.10) + github-pages-health-check (= 1.8.1) + jekyll (= 3.7.4) + jekyll-avatar (= 0.6.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.1.5) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.2) - jekyll-gist (= 1.4.1) - jekyll-github-metadata (= 2.9.1) - jekyll-mentions (= 1.2.0) - jekyll-optional-front-matter (= 0.2.0) + jekyll-feed (= 0.10.0) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.9.4) + jekyll-mentions (= 1.4.1) + jekyll-optional-front-matter (= 0.3.0) jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.1.0) - jekyll-redirect-from (= 0.12.1) - jekyll-relative-links (= 0.4.1) - jekyll-sass-converter (= 1.5.0) - jekyll-seo-tag (= 2.3.0) - jekyll-sitemap (= 1.0.0) + jekyll-readme-index (= 0.2.0) + jekyll-redirect-from (= 0.14.0) + jekyll-relative-links (= 0.5.3) + jekyll-remote-theme (= 0.3.1) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.5.0) + jekyll-sitemap (= 1.2.0) jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.1.0) - jekyll-theme-cayman (= 0.1.0) - jekyll-theme-dinky (= 0.1.0) - jekyll-theme-hacker (= 0.1.0) - jekyll-theme-leap-day (= 0.1.0) - jekyll-theme-merlot (= 0.1.0) - jekyll-theme-midnight (= 0.1.0) - jekyll-theme-minimal (= 0.1.0) - jekyll-theme-modernist (= 0.1.0) - jekyll-theme-primer (= 0.5.2) - jekyll-theme-slate (= 0.1.0) - jekyll-theme-tactile (= 0.1.0) - jekyll-theme-time-machine (= 0.1.0) - jekyll-titles-from-headings (= 0.4.0) - jemoji (= 0.8.0) - kramdown (= 1.13.2) + jekyll-theme-architect (= 0.1.1) + jekyll-theme-cayman (= 0.1.1) + jekyll-theme-dinky (= 0.1.1) + jekyll-theme-hacker (= 0.1.1) + jekyll-theme-leap-day (= 0.1.1) + jekyll-theme-merlot (= 0.1.1) + jekyll-theme-midnight (= 0.1.1) + jekyll-theme-minimal (= 0.1.1) + jekyll-theme-modernist (= 0.1.1) + jekyll-theme-primer (= 0.5.3) + jekyll-theme-slate (= 0.1.1) + jekyll-theme-tactile (= 0.1.1) + jekyll-theme-time-machine (= 0.1.1) + jekyll-titles-from-headings (= 0.5.1) + jemoji (= 0.10.1) + kramdown (= 1.17.0) liquid (= 4.0.0) - listen (= 3.0.6) + listen (= 3.1.5) mercenary (~> 0.3) - minima (= 2.1.1) - rouge (= 1.11.1) + minima (= 2.5.0) + nokogiri (>= 1.8.2, < 2.0) + rouge (= 2.2.1) terminal-table (~> 1.4) - github-pages-health-check (1.3.5) + github-pages-health-check (1.8.1) addressable (~> 2.3) - net-dns (~> 0.8) + dnsruby (~> 1.60) octokit (~> 4.0) public_suffix (~> 2.0) - typhoeus (~> 0.7) - html-pipeline (2.7.0) + typhoeus (~> 1.3) + html-pipeline (2.8.4) activesupport (>= 2) nokogiri (>= 1.4) - i18n (0.8.6) - jekyll (3.5.2) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.7.4) addressable (~> 2.4) colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 1.1) - kramdown (~> 1.3) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) - rouge (~> 1.7) + rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.4.2) + jekyll-avatar (0.6.0) jekyll (~> 3.0) - jekyll-coffeescript (1.0.1) + jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.2.0) + commonmarker (~> 0.14) + jekyll (>= 3.0, < 4.0) + jekyll-commonmark-ghpages (0.1.5) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1) + rouge (~> 2) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.9.2) + jekyll-feed (0.10.0) jekyll (~> 3.3) - jekyll-gist (1.4.1) + jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.1) + jekyll-github-metadata (2.9.4) jekyll (~> 3.1) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.2.0) - activesupport (~> 4.0) + jekyll-mentions (1.4.1) html-pipeline (~> 2.3) jekyll (~> 3.0) - jekyll-optional-front-matter (0.2.0) + jekyll-optional-front-matter (0.3.0) jekyll (~> 3.0) jekyll-paginate (1.1.0) - jekyll-readme-index (0.1.0) + jekyll-readme-index (0.2.0) jekyll (~> 3.0) - jekyll-redirect-from (0.12.1) + jekyll-redirect-from (0.14.0) jekyll (~> 3.3) - jekyll-relative-links (0.4.1) + jekyll-relative-links (0.5.3) jekyll (~> 3.3) - jekyll-sass-converter (1.5.0) + jekyll-remote-theme (0.3.1) + jekyll (~> 3.5) + rubyzip (>= 1.2.1, < 3.0) + jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.3.0) + jekyll-seo-tag (2.5.0) jekyll (~> 3.3) - jekyll-sitemap (1.0.0) + jekyll-sitemap (1.2.0) jekyll (~> 3.3) jekyll-swiss (0.4.0) - jekyll-theme-architect (0.1.0) + jekyll-theme-architect (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.0) + jekyll-theme-cayman (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.0) + jekyll-theme-dinky (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.0) + jekyll-theme-hacker (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.0) + jekyll-theme-leap-day (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.0) + jekyll-theme-merlot (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.0) + jekyll-theme-midnight (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.0) + jekyll-theme-minimal (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.0) + jekyll-theme-modernist (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.2) + jekyll-theme-primer (0.5.3) jekyll (~> 3.5) jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.2) - jekyll-theme-slate (0.1.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.0) + jekyll-theme-tactile (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.0) + jekyll-theme-time-machine (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.4.0) + jekyll-titles-from-headings (0.5.1) jekyll (~> 3.3) - jekyll-watch (1.5.0) - listen (~> 3.0, < 3.1) - jemoji (0.8.0) - activesupport (~> 4.0) + jekyll-watch (2.0.0) + listen (~> 3.0) + jemoji (0.10.1) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (>= 3.0) - kramdown (1.13.2) + jekyll (~> 3.0) + kramdown (1.17.0) liquid (4.0.0) - listen (3.0.6) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9.7) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) mercenary (0.3.6) mini_portile2 (2.3.0) - minima (2.1.1) - jekyll (~> 3.3) - minitest (5.10.3) + minima (2.5.0) + jekyll (~> 3.5) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.11.3) multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (1.8.2) + nokogiri (1.8.4) mini_portile2 (~> 2.3.0) - octokit (4.7.0) + octokit (4.12.0) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.14.0) + pathutil (0.16.1) forwardable-extended (~> 2.6) public_suffix (2.0.5) - rb-fsevent (0.10.2) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rouge (1.11.1) - rspec (3.6.0) - rspec-core (~> 3.6.0) - rspec-expectations (~> 3.6.0) - rspec-mocks (~> 3.6.0) - rspec-core (3.6.0) - rspec-support (~> 3.6.0) - rspec-expectations (3.6.0) + rouge (2.2.1) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-mocks (3.6.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.6.0) - rspec-support (3.6.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + ruby-enum (0.7.2) + i18n + ruby_dep (1.5.0) + rubyzip (1.2.2) safe_yaml (1.0.4) - sass (3.5.1) + sass (3.6.0) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -215,11 +246,11 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - typhoeus (0.8.0) - ethon (>= 0.8.0) - tzinfo (1.2.3) + typhoeus (1.3.0) + ethon (>= 0.9.0) + tzinfo (1.2.5) thread_safe (~> 0.1) - unicode-display_width (1.3.0) + unicode-display_width (1.4.0) PLATFORMS ruby @@ -230,4 +261,4 @@ DEPENDENCIES rspec BUNDLED WITH - 1.16.1 + 1.16.3 From 099a2e612bd0b76d8170207fd664129a4f6551fa Mon Sep 17 00:00:00 2001 From: khanhub Date: Tue, 25 Dec 2018 19:01:00 +0900 Subject: [PATCH 24/58] add missing code snippet --- _parts/part8.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_parts/part8.md b/_parts/part8.md index 349b1b7..f027b75 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -316,6 +316,7 @@ When we open the database for the first time, the database file will be empty, s Table* table = malloc(sizeof(Table)); table->pager = pager; - table->num_rows = num_rows; ++ table->root_page_num = 0; + + if (pager->num_pages == 0) { + // New database file. Initialize page 0 as leaf node. @@ -701,6 +702,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro Table* table = malloc(sizeof(Table)); table->pager = pager; - table->num_rows = num_rows; ++ table->root_page_num = 0; + + if (pager->num_pages == 0) { + // New database file. Initialize page 0 as leaf node. From 492d77ab2bde5257d0d71fc1485a0ee18a8a3cf7 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Wed, 13 Mar 2019 21:37:21 -0700 Subject: [PATCH 25/58] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c5a4c3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Connor Stack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 32dd2d151fc26f814745e0f9899169d4f4f4c4d0 Mon Sep 17 00:00:00 2001 From: Ryan Luker Date: Fri, 5 Apr 2019 16:46:01 -0700 Subject: [PATCH 26/58] Update part1.md minor grammar cleanup --- _parts/part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part1.md b/_parts/part1.md index 5c4bcec..c79e591 100644 --- a/_parts/part1.md +++ b/_parts/part1.md @@ -35,7 +35,7 @@ The _back-end_ consists of the: - pager - os interface -The **virtual machine** takes bytecode generated by the front-end as instructions. It can then perform operations on one or more tables or indexes, each of which is stored in a data structure called a B-tree. The VM is essentially a big switch statement on the type the bytecode instruction. +The **virtual machine** takes bytecode generated by the front-end as instructions. It can then perform operations on one or more tables or indexes, each of which is stored in a data structure called a B-tree. The VM is essentially a big switch statement on the type of bytecode instruction. Each **B-tree** consists of many nodes. Each node is one page in length. The B-tree can retrieve a page from disk or save it back to disk by issuing commands to the pager. From b9137d226230d305d62911221714f6533f895b36 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Sat, 6 Apr 2019 14:43:41 +0300 Subject: [PATCH 27/58] Free memory of input buffer in Part 1 --- _parts/part1.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/_parts/part1.md b/_parts/part1.md index c79e591..4e21d86 100644 --- a/_parts/part1.md +++ b/_parts/part1.md @@ -72,6 +72,7 @@ int main(int argc, char* argv[]) { read_input(input_buffer); if (strcmp(input_buffer->buffer, ".exit") == 0) { + close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); @@ -109,7 +110,7 @@ To read a line of input, use [getline()](http://man7.org/linux/man-pages/man3/ge ```c ssize_t getline(char **lineptr, size_t *n, FILE *stream); ``` -`lineptr` : a pointer to the variable we use to point to the buffer containing the read line. +`lineptr` : a pointer to the variable we use to point to the buffer containing the read line. If it set to `NULL` it is mallocatted by `getline` and should thus be freed by the user, even if the command fails. `n` : a pointer to the variable we use to save the size of allocated buffer. @@ -137,6 +138,18 @@ void read_input(InputBuffer* input_buffer) { } ``` +Now it is proper to define a function that frees the memory allocated for an +instance of `InputBuffer *` and the `buffer` element of the respective +structure (`getline` allocates memory for `input_buffer->buffer` in +`read_input`). + +```c +void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer->buffer); + free(input_buffer); +} +``` + Finally, we parse and execute the command. There is only one recognized command right now : `.exit`, which terminates the program. Otherwise we print an error message and continue the loop. ```c @@ -196,6 +209,11 @@ void read_input(InputBuffer* input_buffer) { input_buffer->buffer[bytes_read - 1] = 0; } +void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer->buffer); + free(input_buffer); +} + int main(int argc, char* argv[]) { InputBuffer* input_buffer = new_input_buffer(); while (true) { @@ -203,6 +221,7 @@ int main(int argc, char* argv[]) { read_input(input_buffer); if (strcmp(input_buffer->buffer, ".exit") == 0) { + close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); From 84d1ede4c515d9877084924e5ee7e42e4bd5920a Mon Sep 17 00:00:00 2001 From: Niles Rogoff Date: Sat, 6 Apr 2019 22:54:28 -0400 Subject: [PATCH 28/58] Fix if statement based off of uninitialized memory in part3 --- _parts/part3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index 86033ae..67c05ba 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -185,7 +185,7 @@ Lastly, we need to initialize the table and handle a few more error cases: ```diff + Table* new_table() { -+ Table* table = malloc(sizeof(Table)); ++ Table* table = calloc(sizeof(Table)); + table->num_rows = 0; + + return table; @@ -334,7 +334,7 @@ We'll address those issues in the next part. For now, here's the complete diff f +} + +Table* new_table() { -+ Table* table = malloc(sizeof(Table)); ++ Table* table = calloc(sizeof(Table)); + table->num_rows = 0; + + return table; @@ -426,4 +426,4 @@ We'll address those issues in the next part. For now, here's the complete diff f + } } } -``` \ No newline at end of file +``` From c3d5432f251814e6726dd483ae27635035b1c4b6 Mon Sep 17 00:00:00 2001 From: Niles Rogoff Date: Sat, 6 Apr 2019 22:56:44 -0400 Subject: [PATCH 29/58] Update later parts to match earlier changes --- _parts/part5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part5.md b/_parts/part5.md index 9659539..fa6ed47 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -66,7 +66,7 @@ I'm renaming `new_table()` to `db_open()` because it now has the effect of openi + Pager* pager = pager_open(filename); + uint32_t num_rows = pager->file_length / ROW_SIZE; + - Table* table = malloc(sizeof(Table)); + Table* table = calloc(sizeof(Table)); - table->num_rows = 0; + table->pager = pager; + table->num_rows = num_rows; @@ -413,7 +413,7 @@ Until then! + Pager* pager = pager_open(filename); + uint32_t num_rows = pager->file_length / ROW_SIZE; + - Table* table = malloc(sizeof(Table)); + Table* table = calloc(sizeof(Table)); - table->num_rows = 0; + table->pager = pager; + table->num_rows = num_rows; From adae8bd976a743459c2c9d2ef7f8dbee01f1c34e Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Mon, 8 Apr 2019 11:06:29 +0300 Subject: [PATCH 30/58] Update part1.md --- _parts/part1.md | 1 + 1 file changed, 1 insertion(+) diff --git a/_parts/part1.md b/_parts/part1.md index 4e21d86..2535dd6 100644 --- a/_parts/part1.md +++ b/_parts/part1.md @@ -154,6 +154,7 @@ Finally, we parse and execute the command. There is only one recognized command ```c if (strcmp(input_buffer->buffer, ".exit") == 0) { + close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("Unrecognized command '%s'.\n", input_buffer->buffer); From 4f8d40aa322cee934a521e48b8644d301d81d766 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Mon, 8 Apr 2019 14:46:40 +0300 Subject: [PATCH 31/58] Update part2.md final diff --- _parts/part2.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/_parts/part2.md b/_parts/part2.md index 074b4a8..225120a 100644 --- a/_parts/part2.md +++ b/_parts/part2.md @@ -176,12 +176,13 @@ The skeleton of our database is taking shape... wouldn't it be nice if it stored InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; -@@ -35,16 +52,66 @@ void read_input(InputBuffer* input_buffer) { - input_buffer->buffer[bytes_read - 1] = 0; +@@ -40,17 +57,67 @@ void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer); } +MetaCommandResult do_meta_command(InputBuffer* input_buffer) { + if (strcmp(input_buffer->buffer, ".exit") == 0) { ++ close_input_buffer(input_buffer); + exit(EXIT_SUCCESS); + } else { + return META_COMMAND_UNRECOGNIZED_COMMAND; @@ -220,6 +221,7 @@ The skeleton of our database is taking shape... wouldn't it be nice if it stored read_input(input_buffer); - if (strcmp(input_buffer->buffer, ".exit") == 0) { +- close_input_buffer(input_buffer); - exit(EXIT_SUCCESS); - } else { - printf("Unrecognized command '%s'.\n", input_buffer->buffer); @@ -247,4 +249,4 @@ The skeleton of our database is taking shape... wouldn't it be nice if it stored + printf("Executed.\n"); } } -``` \ No newline at end of file +``` From 6a75735b12e05ed8d7ffebc622f5104151b58fbe Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Tue, 9 Apr 2019 11:35:49 +0300 Subject: [PATCH 32/58] Update part3.md * Include * Fix 'variably modified array at file scope' error * Ensure page-pointers are initialized to NULL before attempting executing statements * Ensure table memory is released on exit --- _parts/part3.md | 286 +++++++++++++++++++++++++++++------------------- 1 file changed, 172 insertions(+), 114 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index 67c05ba..690bd2c 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -44,8 +44,8 @@ That means we need to upgrade our `prepare_statement` function to parse argument We store those parsed arguments into a new `Row` data structure inside the statement object: ```diff -+const uint32_t COLUMN_USERNAME_SIZE = 32; -+const uint32_t COLUMN_EMAIL_SIZE = 255; ++#define COLUMN_USERNAME_SIZE = 32; ++#define COLUMN_EMAIL_SIZE = 255; +struct Row_t { + uint32_t id; + char username[COLUMN_USERNAME_SIZE]; @@ -115,8 +115,8 @@ Next, a `Table` structure that points to pages of rows and keeps track of how ma +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + +struct Table_t { -+ void* pages[TABLE_MAX_PAGES]; + uint32_t num_rows; ++ void** pages; +}; +typedef struct Table_t Table; ``` @@ -132,7 +132,7 @@ Speaking of which, here is how we figure out where to read/write in memory for a +void* row_slot(Table* table, uint32_t row_num) { + uint32_t page_num = row_num / ROWS_PER_PAGE; + void* page = table->pages[page_num]; -+ if (!page) { ++ if (page = NULL) { + // Allocate memory only when we try to access page + page = table->pages[page_num] = malloc(PAGE_SIZE); + } @@ -181,15 +181,26 @@ Now we can make `execute_statement` read/write from our table structure: } ``` -Lastly, we need to initialize the table and handle a few more error cases: +Lastly, we need to initialize the table, create the respective +memory release function and handle a few more error cases: ```diff + Table* new_table() { -+ Table* table = calloc(sizeof(Table)); ++ Table* table = malloc(sizeof(Table)); + table->num_rows = 0; -+ ++ // Allocate space for the pointers to the pages ++ // and initialize them to NULL ++ table->pages = calloc(TABLE_MAX_PAGES, sizeof(void *)); + return table; +} ++ ++void free_table(Table* table) { ++ for (int i = 0; table->pages[i]; i++) { ++ free(table->pages[i]); ++ } ++ free(table->pages); ++ free(table); ++} ``` ```diff int main(int argc, char* argv[]) { @@ -247,43 +258,52 @@ Now would be a great time to write some tests, for a couple reasons: We'll address those issues in the next part. For now, here's the complete diff from this part: ```diff +@@ -2,6 +2,7 @@ + #include + #include + #include ++#include + + struct InputBuffer_t { + char* buffer; +@@ -10,6 +11,106 @@ struct InputBuffer_t { + }; typedef struct InputBuffer_t InputBuffer; - + +enum ExecuteResult_t { EXECUTE_SUCCESS, EXECUTE_TABLE_FULL }; +typedef enum ExecuteResult_t ExecuteResult; + - enum MetaCommandResult_t { - META_COMMAND_SUCCESS, - META_COMMAND_UNRECOGNIZED_COMMAND - }; - typedef enum MetaCommandResult_t MetaCommandResult; - --enum PrepareResult_t { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT }; ++enum MetaCommandResult_t { ++ META_COMMAND_SUCCESS, ++ META_COMMAND_UNRECOGNIZED_COMMAND ++}; ++typedef enum MetaCommandResult_t MetaCommandResult; ++ +enum PrepareResult_t { + PREPARE_SUCCESS, + PREPARE_SYNTAX_ERROR, + PREPARE_UNRECOGNIZED_STATEMENT -+}; - typedef enum PrepareResult_t PrepareResult; - - enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; - typedef enum StatementType_t StatementType; - -+const uint32_t COLUMN_USERNAME_SIZE = 32; -+const uint32_t COLUMN_EMAIL_SIZE = 255; ++ }; ++typedef enum PrepareResult_t PrepareResult; ++ ++enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; ++typedef enum StatementType_t StatementType; ++ ++#define COLUMN_USERNAME_SIZE 32 ++#define COLUMN_EMAIL_SIZE 255 +struct Row_t { -+ uint32_t id; -+ char username[COLUMN_USERNAME_SIZE]; -+ char email[COLUMN_EMAIL_SIZE]; ++ uint32_t id; ++ char username[COLUMN_USERNAME_SIZE]; ++ char email[COLUMN_EMAIL_SIZE]; +}; +typedef struct Row_t Row; + - struct Statement_t { - StatementType type; -+ Row row_to_insert; // only used by insert statement - }; - typedef struct Statement_t Statement; - ++struct Statement_t { ++ StatementType type; ++ Row row_to_insert; //only used by insert statement ++}; ++typedef struct Statement_t Statement; ++ +#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute) + +const uint32_t ID_SIZE = size_of_attribute(Row, id); @@ -300,130 +320,168 @@ We'll address those issues in the next part. For now, here's the complete diff f +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + +struct Table_t { -+ void* pages[TABLE_MAX_PAGES]; -+ uint32_t num_rows; ++ uint32_t num_rows; ++ void** pages; +}; +typedef struct Table_t Table; + +void print_row(Row* row) { -+ printf("(%d, %s, %s)\n", row->id, row->username, row->email); ++ printf("(%d, %s, %s)\n", row->id, row->username, row->email); +} + +void serialize_row(Row* source, void* destination) { -+ memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); -+ memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); -+ memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); ++ memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); ++ memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); ++ memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); +} + -+void deserialize_row(void* source, Row* destination) { -+ memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); -+ memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); -+ memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); ++void deserialize_row(void *source, Row* destination) { ++ memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); ++ memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); ++ memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); +} + +void* row_slot(Table* table, uint32_t row_num) { -+ uint32_t page_num = row_num / ROWS_PER_PAGE; -+ void* page = table->pages[page_num]; -+ if (!page) { -+ // Allocate memory only when we try to access page -+ page = table->pages[page_num] = malloc(PAGE_SIZE); -+ } -+ uint32_t row_offset = row_num % ROWS_PER_PAGE; -+ uint32_t byte_offset = row_offset * ROW_SIZE; -+ return page + byte_offset; ++ uint32_t page_num = row_num / ROWS_PER_PAGE; ++ void *page = table->pages[page_num]; ++ if (page == NULL) { ++ // Allocate memory only when we try to access page ++ page = table->pages[page_num] = malloc(PAGE_SIZE); ++ } ++ uint32_t row_offset = row_num % ROWS_PER_PAGE; ++ uint32_t byte_offset = row_offset * ROW_SIZE; ++ return page + byte_offset; +} + +Table* new_table() { -+ Table* table = calloc(sizeof(Table)); -+ table->num_rows = 0; ++ Table* table = malloc(sizeof(Table)); ++ table->num_rows = 0; ++ // Allocate space for the pointers to the pages ++ // and initialize them to NULL ++ table->pages = calloc(TABLE_MAX_PAGES, sizeof(void *)); ++ return table; ++} + -+ return table; ++void free_table(Table* table) { ++ for (int i = 0; table->pages[i]; i++) { ++ free(table->pages[i]); ++ } ++ free(table->pages); ++ free(table); +} + InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; -@@ -64,6 +137,12 @@ PrepareResult prepare_statement(InputBuffer* input_buffer, - Statement* statement) { - if (strncmp(input_buffer->buffer, "insert", 6) == 0) { - statement->type = STATEMENT_INSERT; +@@ -40,17 +141,105 @@ void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer); + } + ++MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table *table) { ++ if (strcmp(input_buffer->buffer, ".exit") == 0) { ++ close_input_buffer(input_buffer); ++ free_table(table); ++ exit(EXIT_SUCCESS); ++ } else { ++ return META_COMMAND_UNRECOGNIZED_COMMAND; ++ } ++} ++ ++PrepareResult prepare_statement(InputBuffer* input_buffer, ++ Statement* statement) { ++ if (strncmp(input_buffer->buffer, "insert", 6) == 0) { ++ statement->type = STATEMENT_INSERT; + int args_assigned = sscanf( -+ input_buffer->buffer, "insert %d %s %s", &(statement->row_to_insert.id), -+ statement->row_to_insert.username, statement->row_to_insert.email); ++ input_buffer->buffer, "insert %d %s %s", &(statement->row_to_insert.id), ++ statement->row_to_insert.username, statement->row_to_insert.email ++ ); + if (args_assigned < 3) { -+ return PREPARE_SYNTAX_ERROR; ++ return PREPARE_SYNTAX_ERROR; + } - return PREPARE_SUCCESS; - } - if (strcmp(input_buffer->buffer, "select") == 0) { -@@ -74,18 +153,39 @@ PrepareResult prepare_statement(InputBuffer* input_buffer, - return PREPARE_UNRECOGNIZED_STATEMENT; - } - --void execute_statement(Statement* statement) { -+ExecuteResult execute_insert(Statement* statement, Table* table) { -+ if (table->num_rows >= TABLE_MAX_ROWS) { -+ return EXECUTE_TABLE_FULL; ++ return PREPARE_SUCCESS; ++ } ++ if (strcmp(input_buffer->buffer, "select") == 0) { ++ statement->type = STATEMENT_SELECT; ++ return PREPARE_SUCCESS; + } + -+ Row* row_to_insert = &(statement->row_to_insert); ++ return PREPARE_UNRECOGNIZED_STATEMENT; ++} + -+ serialize_row(row_to_insert, row_slot(table, table->num_rows)); -+ table->num_rows += 1; ++ExecuteResult execute_insert(Statement* statement, Table* table) { ++ if (table->num_rows >= TABLE_MAX_ROWS) { ++ return EXECUTE_TABLE_FULL; ++ } + -+ return EXECUTE_SUCCESS; ++ Row* row_to_insert = &(statement->row_to_insert); ++ ++ serialize_row(row_to_insert, row_slot(table, table->num_rows)); ++ table->num_rows += 1; ++ ++ return EXECUTE_SUCCESS; +} + +ExecuteResult execute_select(Statement* statement, Table* table) { -+ Row row; -+ for (uint32_t i = 0; i < table->num_rows; i++) { -+ deserialize_row(row_slot(table, i), &row); -+ print_row(&row); ++ Row row; ++ for (uint32_t i = 0; i < table->num_rows; i++) { ++ deserialize_row(row_slot(table, i), &row); ++ print_row(&row); ++ } ++ return EXECUTE_SUCCESS; ++} ++ ++ExecuteResult execute_statement(Statement* statement, Table *table) { ++ switch (statement->type) { ++ case (STATEMENT_INSERT): ++ return execute_insert(statement, table); ++ case (STATEMENT_SELECT): ++ return execute_select(statement, table); + } -+ return EXECUTE_SUCCESS; +} + -+ExecuteResult execute_statement(Statement* statement, Table* table) { - switch (statement->type) { - case (STATEMENT_INSERT): -- printf("This is where we would do an insert.\n"); -- break; -+ return execute_insert(statement, table); - case (STATEMENT_SELECT): -- printf("This is where we would do a select.\n"); -- break; -+ return execute_select(statement, table); - } - } - int main(int argc, char* argv[]) { + Table* table = new_table(); InputBuffer* input_buffer = new_input_buffer(); while (true) { print_prompt(); -@@ -105,13 +205,22 @@ int main(int argc, char* argv[]) { - switch (prepare_statement(input_buffer, &statement)) { - case (PREPARE_SUCCESS): - break; + read_input(input_buffer); + +- if (strcmp(input_buffer->buffer, ".exit") == 0) { +- close_input_buffer(input_buffer); +- exit(EXIT_SUCCESS); +- } else { +- printf("Unrecognized command '%s'.\n", input_buffer->buffer); ++ if (input_buffer->buffer[0] == '.') { ++ switch (do_meta_command(input_buffer, table)) { ++ case (META_COMMAND_SUCCESS): ++ continue; ++ case (META_COMMAND_UNRECOGNIZED_COMMAND): ++ printf("Unrecognized command '%s'\n", input_buffer->buffer); ++ continue; ++ } ++ } ++ ++ Statement statement; ++ switch (prepare_statement(input_buffer, &statement)) { ++ case (PREPARE_SUCCESS): ++ break; + case (PREPARE_SYNTAX_ERROR): -+ printf("Syntax error. Could not parse statement.\n"); ++ printf("Syntax error. Could not parse statement.\n"); ++ continue; ++ case (PREPARE_UNRECOGNIZED_STATEMENT): ++ printf("Unrecognized keyword at start of '%s'.\n", ++ input_buffer->buffer); + continue; - case (PREPARE_UNRECOGNIZED_STATEMENT): - printf("Unrecognized keyword at start of '%s'.\n", - input_buffer->buffer); - continue; - } - -- execute_statement(&statement); -- printf("Executed.\n"); -+ switch (execute_statement(&statement, table)) { -+ case (EXECUTE_SUCCESS): -+ printf("Executed.\n"); -+ break; -+ case (EXECUTE_TABLE_FULL): -+ printf("Error: Table full.\n"); -+ break; + } ++ ++ switch (execute_statement(&statement, table)) { ++ case (EXECUTE_SUCCESS): ++ printf("Executed.\n"); ++ break; ++ case (EXECUTE_TABLE_FULL): ++ printf("Error: Table full.\n"); ++ break; + } } } ``` From 07ef8aa8b4eadc1874eb677a41e672513825b562 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Wed, 10 Apr 2019 11:38:19 +0300 Subject: [PATCH 33/58] Store table->pages on the stack --- _parts/part3.md | 106 ++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index 690bd2c..8ceca49 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -110,13 +110,13 @@ We also need code to convert to and from the compact representation. Next, a `Table` structure that points to pages of rows and keeps track of how many rows there are: ```diff +const uint32_t PAGE_SIZE = 4096; -+const uint32_t TABLE_MAX_PAGES = 100; ++#define TABLE_MAX_PAGES 100 +const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + +struct Table_t { + uint32_t num_rows; -+ void** pages; ++ void* pages[TABLE_MAX_PAGES]; +}; +typedef struct Table_t Table; ``` @@ -188,9 +188,9 @@ memory release function and handle a few more error cases: + Table* new_table() { + Table* table = malloc(sizeof(Table)); + table->num_rows = 0; -+ // Allocate space for the pointers to the pages -+ // and initialize them to NULL -+ table->pages = calloc(TABLE_MAX_PAGES, sizeof(void *)); ++ for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { ++ table->pages[i] = NULL; ++ } + return table; +} + @@ -198,7 +198,6 @@ memory release function and handle a few more error cases: + for (int i = 0; table->pages[i]; i++) { + free(table->pages[i]); + } -+ free(table->pages); + free(table); +} ``` @@ -266,7 +265,7 @@ We'll address those issues in the next part. For now, here's the complete diff f struct InputBuffer_t { char* buffer; -@@ -10,6 +11,106 @@ struct InputBuffer_t { +@@ -10,6 +11,105 @@ struct InputBuffer_t { }; typedef struct InputBuffer_t InputBuffer; @@ -292,9 +291,9 @@ We'll address those issues in the next part. For now, here's the complete diff f +#define COLUMN_USERNAME_SIZE 32 +#define COLUMN_EMAIL_SIZE 255 +struct Row_t { -+ uint32_t id; -+ char username[COLUMN_USERNAME_SIZE]; -+ char email[COLUMN_EMAIL_SIZE]; ++ uint32_t id; ++ char username[COLUMN_USERNAME_SIZE]; ++ char email[COLUMN_EMAIL_SIZE]; +}; +typedef struct Row_t Row; + @@ -315,65 +314,64 @@ We'll address those issues in the next part. For now, here's the complete diff f +const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE; + +const uint32_t PAGE_SIZE = 4096; -+const uint32_t TABLE_MAX_PAGES = 100; ++#define TABLE_MAX_PAGES 100 +const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + +struct Table_t { -+ uint32_t num_rows; -+ void** pages; ++ uint32_t num_rows; ++ void* pages[TABLE_MAX_PAGES]; +}; +typedef struct Table_t Table; + +void print_row(Row* row) { -+ printf("(%d, %s, %s)\n", row->id, row->username, row->email); ++ printf("(%d, %s, %s)\n", row->id, row->username, row->email); +} + +void serialize_row(Row* source, void* destination) { -+ memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); -+ memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); -+ memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); ++ memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); ++ memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); ++ memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); +} + +void deserialize_row(void *source, Row* destination) { -+ memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); -+ memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); -+ memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); ++ memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE); ++ memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE); ++ memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); +} + +void* row_slot(Table* table, uint32_t row_num) { -+ uint32_t page_num = row_num / ROWS_PER_PAGE; -+ void *page = table->pages[page_num]; -+ if (page == NULL) { -+ // Allocate memory only when we try to access page -+ page = table->pages[page_num] = malloc(PAGE_SIZE); -+ } -+ uint32_t row_offset = row_num % ROWS_PER_PAGE; -+ uint32_t byte_offset = row_offset * ROW_SIZE; -+ return page + byte_offset; ++ uint32_t page_num = row_num / ROWS_PER_PAGE; ++ void *page = table->pages[page_num]; ++ if (page == NULL) { ++ // Allocate memory only when we try to access page ++ page = table->pages[page_num] = malloc(PAGE_SIZE); ++ } ++ uint32_t row_offset = row_num % ROWS_PER_PAGE; ++ uint32_t byte_offset = row_offset * ROW_SIZE; ++ return page + byte_offset; +} + +Table* new_table() { -+ Table* table = malloc(sizeof(Table)); -+ table->num_rows = 0; -+ // Allocate space for the pointers to the pages -+ // and initialize them to NULL -+ table->pages = calloc(TABLE_MAX_PAGES, sizeof(void *)); -+ return table; ++ Table* table = malloc(sizeof(Table)); ++ table->num_rows = 0; ++ for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { ++ table->pages[i] = NULL; ++ } ++ return table; +} + +void free_table(Table* table) { -+ for (int i = 0; table->pages[i]; i++) { -+ free(table->pages[i]); -+ } -+ free(table->pages); -+ free(table); ++ for (int i = 0; table->pages[i]; i++) { ++ free(table->pages[i]); ++ } ++ free(table); +} + InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; -@@ -40,17 +141,105 @@ void close_input_buffer(InputBuffer* input_buffer) { +@@ -40,17 +140,105 @@ void close_input_buffer(InputBuffer* input_buffer) { free(input_buffer); } @@ -409,25 +407,25 @@ We'll address those issues in the next part. For now, here's the complete diff f +} + +ExecuteResult execute_insert(Statement* statement, Table* table) { -+ if (table->num_rows >= TABLE_MAX_ROWS) { -+ return EXECUTE_TABLE_FULL; -+ } ++ if (table->num_rows >= TABLE_MAX_ROWS) { ++ return EXECUTE_TABLE_FULL; ++ } + -+ Row* row_to_insert = &(statement->row_to_insert); ++ Row* row_to_insert = &(statement->row_to_insert); + -+ serialize_row(row_to_insert, row_slot(table, table->num_rows)); -+ table->num_rows += 1; ++ serialize_row(row_to_insert, row_slot(table, table->num_rows)); ++ table->num_rows += 1; + -+ return EXECUTE_SUCCESS; ++ return EXECUTE_SUCCESS; +} + +ExecuteResult execute_select(Statement* statement, Table* table) { -+ Row row; -+ for (uint32_t i = 0; i < table->num_rows; i++) { -+ deserialize_row(row_slot(table, i), &row); -+ print_row(&row); -+ } -+ return EXECUTE_SUCCESS; ++ Row row; ++ for (uint32_t i = 0; i < table->num_rows; i++) { ++ deserialize_row(row_slot(table, i), &row); ++ print_row(&row); ++ } ++ return EXECUTE_SUCCESS; +} + +ExecuteResult execute_statement(Statement* statement, Table *table) { From d876834bb3724901e86472a107b2210e442ff15b Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Wed, 10 Apr 2019 16:06:05 +0300 Subject: [PATCH 34/58] Update diff in part4 --- _parts/part4.md | 70 +++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/_parts/part4.md b/_parts/part4.md index a64de62..1227103 100644 --- a/_parts/part4.md +++ b/_parts/part4.md @@ -305,15 +305,17 @@ It's gonna be great. Here's the complete diff for this part: ```diff +@@ -22,6 +22,8 @@ typedef enum MetaCommandResult_t MetaCommandResult; + enum PrepareResult_t { PREPARE_SUCCESS, + PREPARE_NEGATIVE_ID, + PREPARE_STRING_TOO_LONG, PREPARE_SYNTAX_ERROR, PREPARE_UNRECOGNIZED_STATEMENT - }; -@@ -33,8 +35,8 @@ const uint32_t COLUMN_USERNAME_SIZE = 32; - const uint32_t COLUMN_EMAIL_SIZE = 255; + }; +@@ -34,8 +36,8 @@ typedef enum StatementType_t StatementType; + #define COLUMN_EMAIL_SIZE 255 struct Row_t { uint32_t id; - char username[COLUMN_USERNAME_SIZE]; @@ -322,13 +324,21 @@ Here's the complete diff for this part: + char email[COLUMN_EMAIL_SIZE + 1]; }; typedef struct Row_t Row; - -@@ -133,17 +135,40 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer) { + +@@ -150,18 +152,40 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table *table) { } } - + +-PrepareResult prepare_statement(InputBuffer* input_buffer, +- Statement* statement) { +- if (strncmp(input_buffer->buffer, "insert", 6) == 0) { +PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement) { -+ statement->type = STATEMENT_INSERT; + statement->type = STATEMENT_INSERT; +- int args_assigned = sscanf( +- input_buffer->buffer, "insert %d %s %s", &(statement->row_to_insert.id), +- statement->row_to_insert.username, statement->row_to_insert.email +- ); +- if (args_assigned < 3) { + + char* keyword = strtok(input_buffer->buffer, " "); + char* id_string = strtok(NULL, " "); @@ -336,55 +346,47 @@ Here's the complete diff for this part: + char* email = strtok(NULL, " "); + + if (id_string == NULL || username == NULL || email == NULL) { -+ return PREPARE_SYNTAX_ERROR; -+ } + return PREPARE_SYNTAX_ERROR; + } + + int id = atoi(id_string); + if (id < 0) { -+ return PREPARE_NEGATIVE_ID; ++ return PREPARE_NEGATIVE_ID; + } + if (strlen(username) > COLUMN_USERNAME_SIZE) { -+ return PREPARE_STRING_TOO_LONG; ++ return PREPARE_STRING_TOO_LONG; + } + if (strlen(email) > COLUMN_EMAIL_SIZE) { -+ return PREPARE_STRING_TOO_LONG; ++ return PREPARE_STRING_TOO_LONG; + } + + statement->row_to_insert.id = id; + strcpy(statement->row_to_insert.username, username); + strcpy(statement->row_to_insert.email, email); + -+ return PREPARE_SUCCESS; -+} + return PREPARE_SUCCESS; + - PrepareResult prepare_statement(InputBuffer* input_buffer, - Statement* statement) { - if (strncmp(input_buffer->buffer, "insert", 6) == 0) { -- statement->type = STATEMENT_INSERT; -- int args_assigned = sscanf( -- input_buffer->buffer, "insert %d %s %s", &(statement->row_to_insert.id), -- statement->row_to_insert.username, statement->row_to_insert.email); -- if (args_assigned < 3) { -- return PREPARE_SYNTAX_ERROR; -- } -- return PREPARE_SUCCESS; -+ return prepare_insert(input_buffer, statement); ++} ++PrepareResult prepare_statement(InputBuffer* input_buffer, ++ Statement* statement) { ++ if (strncmp(input_buffer->buffer, "insert", 6) == 0) { ++ return prepare_insert(input_buffer, statement); } if (strcmp(input_buffer->buffer, "select") == 0) { statement->type = STATEMENT_SELECT; -@@ -205,6 +230,12 @@ int main(int argc, char* argv[]) { +@@ -223,6 +247,12 @@ int main(int argc, char* argv[]) { switch (prepare_statement(input_buffer, &statement)) { case (PREPARE_SUCCESS): break; + case (PREPARE_NEGATIVE_ID): -+ printf("ID must be positive.\n"); -+ continue; ++ printf("ID must be positive.\n"); ++ continue; + case (PREPARE_STRING_TOO_LONG): -+ printf("String is too long.\n"); -+ continue; ++ printf("String is too long.\n"); ++ continue; case (PREPARE_SYNTAX_ERROR): - printf("Syntax error. Could not parse statement.\n"); - continue; + printf("Syntax error. Could not parse statement.\n"); + continue; ``` And we added tests: ```diff @@ -474,4 +476,4 @@ And we added tests: + ]) + end +end -``` \ No newline at end of file +``` From 0e2de3262e70cf69391ab84c37cbbaf3c109904b Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Wed, 10 Apr 2019 16:28:28 +0300 Subject: [PATCH 35/58] Update part5 * Free table in db_close * Add note about initialization of row bytes * Revise to comply with revisions in previous parts * Update total diff --- Gemfile.lock | 2 +- _parts/part5.md | 283 +++++++++++++++++++++++------------------------- 2 files changed, 136 insertions(+), 149 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f385382..bb3d412 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,4 +261,4 @@ DEPENDENCIES rspec BUNDLED WITH - 1.16.3 + 2.0.1 diff --git a/_parts/part5.md b/_parts/part5.md index fa6ed47..8603f84 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -66,7 +66,7 @@ I'm renaming `new_table()` to `db_open()` because it now has the effect of openi + Pager* pager = pager_open(filename); + uint32_t num_rows = pager->file_length / ROW_SIZE; + - Table* table = calloc(sizeof(Table)); + Table* table = malloc(sizeof(Table)); - table->num_rows = 0; + table->pager = pager; + table->num_rows = num_rows; @@ -111,7 +111,7 @@ Following our new abstraction, we move the logic for fetching a page into its ow void* row_slot(Table* table, uint32_t row_num) { uint32_t page_num = row_num / ROWS_PER_PAGE; - void* page = table->pages[page_num]; -- if (!page) { +- if (page == NULL) { - // Allocate memory only when we try to access page - page = table->pages[page_num] = malloc(PAGE_SIZE); - } @@ -204,6 +204,7 @@ For now, we'll wait to flush the cache to disk until the user closes the connect + } + } + free(pager); ++ free(table); +} + -MetaCommandResult do_meta_command(InputBuffer* input_buffer) { @@ -258,7 +259,7 @@ Lastly, we need to accept the filename as a command-line argument. Don't forget while (true) { print_prompt(); read_input(input_buffer); - + if (input_buffer->buffer[0] == '.') { - switch (do_meta_command(input_buffer)) { + switch (do_meta_command(input_buffer, table)) { @@ -290,12 +291,26 @@ vim mydb.db ``` {% include image.html url="assets/images/file-format.png" description="Current File Format" %} -The first four bytes are the id of the first row (4 bytes because we store a uint32_t). It's stored in little-endian byte order, so the least significant byte comes first (01), followed by the higher-order bytes (00 00 00). We used `memcpy()` to copy bytes from our `Row` struct into the page cache, so that means the struct was laid out in memory in little-endian byte order. That's an attribute of the machine I compiled the program for. If we wanted to write a database file on my machine, then read it on a big-endian machine, we'd have to change our `serialize_row()` and `deserialize_row()` methods to always store and read bytes in the same order. +The first four bytes are the id of the first row (4 bytes because we store a `uint32_t`). It's stored in little-endian byte order, so the least significant byte comes first (01), followed by the higher-order bytes (00 00 00). We used `memcpy()` to copy bytes from our `Row` struct into the page cache, so that means the struct was laid out in memory in little-endian byte order. That's an attribute of the machine I compiled the program for. If we wanted to write a database file on my machine, then read it on a big-endian machine, we'd have to change our `serialize_row()` and `deserialize_row()` methods to always store and read bytes in the same order. The next 33 bytes store the username as a null-terminated string. Apparently "cstack" in ASCII hexadecimal is `63 73 74 61 63 6b`, followed by a null character (`00`). The rest of the 33 bytes are unused. The next 256 bytes store the email in the same way. Here we can see some random junk after the terminating null character. This is most likely due to uninitialized memory in our `Row` struct. We copy the entire 256-byte email buffer into the file, including any bytes after the end of the string. Whatever was in memory when we allocated that struct is still there. But since we use a terminating null character, it has no effect on behavior. +**NOTE**: If we wanted to ensure that all bytes are initialized, it would +suffice to use `strncpy` instead of `memcpy` while copying the `username` +and `email` fields of rows in `serialize_row`, like so: + +```diff + void serialize_row(Row* source, void* destination) { + memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); +- memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); +- memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE); ++ strncpy(destination + USERNAME_OFFSET, source->username, USERNAME_SIZE); ++ strncpy(destination + EMAIL_OFFSET, source->email, EMAIL_SIZE); + } +``` + ## Conclusion Alright! We've got persistence. It's not the greatest. For example if you kill the program without typing `.exit`, you lose your changes. Additionally, we're writing all pages back to disk, even pages that haven't changed since we read them from disk. These are issues we can address later. @@ -312,58 +327,60 @@ Until then! #include #include #include + #include +#include - + struct InputBuffer_t { char* buffer; -@@ -61,8 +64,15 @@ const uint32_t TABLE_MAX_PAGES = 100; +@@ -62,9 +65,16 @@ const uint32_t PAGE_SIZE = 4096; const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; - --struct Table_t { + +struct Pager_t { + int file_descriptor; + uint32_t file_length; - void* pages[TABLE_MAX_PAGES]; ++ void* pages[TABLE_MAX_PAGES]; +}; +typedef struct Pager_t Pager; + -+struct Table_t { -+ Pager* pager; + struct Table_t { uint32_t num_rows; +- void* pages[TABLE_MAX_PAGES]; ++ Pager* pager; }; typedef struct Table_t Table; -@@ -83,21 +93,79 @@ void deserialize_row(void* source, Row* destination) { + +@@ -84,32 +94,81 @@ void deserialize_row(void *source, Row* destination) { memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); } - + +void* get_page(Pager* pager, uint32_t page_num) { + if (page_num > TABLE_MAX_PAGES) { -+ printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, -+ TABLE_MAX_PAGES); -+ exit(EXIT_FAILURE); ++ printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, ++ TABLE_MAX_PAGES); ++ exit(EXIT_FAILURE); + } + + if (pager->pages[page_num] == NULL) { -+ // Cache miss. Allocate memory and load from file. -+ void* page = malloc(PAGE_SIZE); -+ uint32_t num_pages = pager->file_length / PAGE_SIZE; -+ -+ // We might save a partial page at the end of the file -+ if (pager->file_length % PAGE_SIZE) { -+ num_pages += 1; -+ } -+ -+ if (page_num <= num_pages) { -+ lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET); -+ ssize_t bytes_read = read(pager->file_descriptor, page, PAGE_SIZE); -+ if (bytes_read == -1) { -+ printf("Error reading file: %d\n", errno); -+ exit(EXIT_FAILURE); -+ } -+ } -+ -+ pager->pages[page_num] = page; ++ // Cache miss. Allocate memory and load from file. ++ void* page = malloc(PAGE_SIZE); ++ uint32_t num_pages = pager->file_length / PAGE_SIZE; ++ ++ // We might save a partial page at the end of the file ++ if (pager->file_length % PAGE_SIZE) { ++ num_pages += 1; ++ } ++ ++ if (page_num <= num_pages) { ++ lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET); ++ ssize_t bytes_read = read(pager->file_descriptor, page, PAGE_SIZE); ++ if (bytes_read == -1) { ++ printf("Error reading file: %d\n", errno); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ pager->pages[page_num] = page; + } + + return pager->pages[page_num]; @@ -371,29 +388,31 @@ Until then! + void* row_slot(Table* table, uint32_t row_num) { uint32_t page_num = row_num / ROWS_PER_PAGE; -- void* page = table->pages[page_num]; -- if (!page) { -- // Allocate memory only when we try to access page -- page = table->pages[page_num] = malloc(PAGE_SIZE); +- void *page = table->pages[page_num]; +- if (page == NULL) { +- // Allocate memory only when we try to access page +- page = table->pages[page_num] = malloc(PAGE_SIZE); - } -+ void* page = get_page(table->pager, page_num); ++ void *page = get_page(table->pager, page_num); uint32_t row_offset = row_num % ROWS_PER_PAGE; uint32_t byte_offset = row_offset * ROW_SIZE; return page + byte_offset; } - + -Table* new_table() { +- Table* table = malloc(sizeof(Table)); +- table->num_rows = 0; +Pager* pager_open(const char* filename) { + int fd = open(filename, -+ O_RDWR | // Read/Write mode -+ O_CREAT, // Create file if it does not exist -+ S_IWUSR | // User write permission -+ S_IRUSR // User read permission -+ ); ++ O_RDWR | // Read/Write mode ++ O_CREAT, // Create file if it does not exist ++ S_IWUSR | // User write permission ++ S_IRUSR // User read permission ++ ); + + if (fd == -1) { -+ printf("Unable to open file\n"); -+ exit(EXIT_FAILURE); ++ printf("Unable to open file\n"); ++ exit(EXIT_FAILURE); + } + + off_t file_length = lseek(fd, 0, SEEK_END); @@ -402,48 +421,57 @@ Until then! + pager->file_descriptor = fd; + pager->file_length = file_length; + -+ for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { -+ pager->pages[i] = NULL; -+ } + for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { +- table->pages[i] = NULL; ++ pager->pages[i] = NULL; + } +- return table; + + return pager; -+} -+ + } + +-void free_table(Table* table) { +- for (int i = 0; table->pages[i]; i++) { +- free(table->pages[i]); +- } +- free(table); +Table* db_open(const char* filename) { + Pager* pager = pager_open(filename); + uint32_t num_rows = pager->file_length / ROW_SIZE; + - Table* table = calloc(sizeof(Table)); -- table->num_rows = 0; ++ Table* table = malloc(sizeof(Table)); + table->pager = pager; + table->num_rows = num_rows; - - return table; ++ ++ return table; } -@@ -127,8 +195,71 @@ void read_input(InputBuffer* input_buffer) { - input_buffer->buffer[bytes_read - 1] = 0; + + InputBuffer* new_input_buffer() { +@@ -142,10 +201,76 @@ void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer); } - --MetaCommandResult do_meta_command(InputBuffer* input_buffer) { + +void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { + if (pager->pages[page_num] == NULL) { -+ printf("Tried to flush null page\n"); -+ exit(EXIT_FAILURE); ++ printf("Tried to flush null page\n"); ++ exit(EXIT_FAILURE); + } + -+ off_t offset = lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET); ++ off_t offset = lseek(pager->file_descriptor, page_num * PAGE_SIZE, ++ SEEK_SET); + + if (offset == -1) { -+ printf("Error seeking: %d\n", errno); -+ exit(EXIT_FAILURE); ++ printf("Error seeking: %d\n", errno); ++ exit(EXIT_FAILURE); + } + -+ ssize_t bytes_written = -+ write(pager->file_descriptor, pager->pages[page_num], size); ++ ssize_t bytes_written = write( ++ pager->file_descriptor, pager->pages[page_num], size ++ ); + + if (bytes_written == -1) { -+ printf("Error writing: %d\n", errno); -+ exit(EXIT_FAILURE); ++ printf("Error writing: %d\n", errno); ++ exit(EXIT_FAILURE); + } +} + @@ -452,55 +480,67 @@ Until then! + uint32_t num_full_pages = table->num_rows / ROWS_PER_PAGE; + + for (uint32_t i = 0; i < num_full_pages; i++) { -+ if (pager->pages[i] == NULL) { -+ continue; -+ } -+ pager_flush(pager, i, PAGE_SIZE); -+ free(pager->pages[i]); -+ pager->pages[i] = NULL; ++ if (pager->pages[i] == NULL) { ++ continue; ++ } ++ pager_flush(pager, i, PAGE_SIZE); ++ free(pager->pages[i]); ++ pager->pages[i] = NULL; + } + + // There may be a partial page to write to the end of the file + // This should not be needed after we switch to a B-tree + uint32_t num_additional_rows = table->num_rows % ROWS_PER_PAGE; + if (num_additional_rows > 0) { -+ uint32_t page_num = num_full_pages; -+ if (pager->pages[page_num] != NULL) { -+ pager_flush(pager, page_num, num_additional_rows * ROW_SIZE); -+ free(pager->pages[page_num]); -+ pager->pages[page_num] = NULL; -+ } ++ uint32_t page_num = num_full_pages; ++ if (pager->pages[page_num] != NULL) { ++ pager_flush(pager, page_num, num_additional_rows * ROW_SIZE); ++ free(pager->pages[page_num]); ++ pager->pages[page_num] = NULL; ++ } + } + + int result = close(pager->file_descriptor); + if (result == -1) { -+ printf("Error closing db file.\n"); -+ exit(EXIT_FAILURE); ++ printf("Error closing db file.\n"); ++ exit(EXIT_FAILURE); + } + for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { -+ void* page = pager->pages[i]; -+ if (page) { -+ free(page); -+ pager->pages[i] = NULL; -+ } ++ void* page = pager->pages[i]; ++ if (page) { ++ free(page); ++ pager->pages[i] = NULL; ++ } + } ++ + free(pager); ++ free(table); +} + -+MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table) { + MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table *table) { if (strcmp(input_buffer->buffer, ".exit") == 0) { + close_input_buffer(input_buffer); +- free_table(table); + db_close(table); exit(EXIT_SUCCESS); } else { return META_COMMAND_UNRECOGNIZED_COMMAND; -@@ -210,14 +341,21 @@ ExecuteResult execute_statement(Statement* statement, Table* table) { +@@ -182,6 +308,7 @@ PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement) { + return PREPARE_SUCCESS; + } - ++ + PrepareResult prepare_statement(InputBuffer* input_buffer, + Statement* statement) { + if (strncmp(input_buffer->buffer, "insert", 6) == 0) { +@@ -227,7 +354,14 @@ ExecuteResult execute_statement(Statement* statement, Table *table) { + } + int main(int argc, char* argv[]) { - Table* table = new_table(); + if (argc < 2) { -+ printf("Must supply a database filename.\n"); -+ exit(EXIT_FAILURE); ++ printf("Must supply a database filename.\n"); ++ exit(EXIT_FAILURE); + } + + char* filename = argv[1]; @@ -509,59 +549,6 @@ Until then! InputBuffer* input_buffer = new_input_buffer(); while (true) { print_prompt(); - read_input(input_buffer); - - if (input_buffer->buffer[0] == '.') { -- switch (do_meta_command(input_buffer)) { -+ switch (do_meta_command(input_buffer, table)) { - case (META_COMMAND_SUCCESS): - continue; - case (META_COMMAND_UNRECOGNIZED_COMMAND): -diff --git a/spec/main_spec.rb b/spec/main_spec.rb -index 21561ce..bc0180a 100644 ---- a/spec/main_spec.rb -+++ b/spec/main_spec.rb -@@ -1,7 +1,11 @@ - describe 'database' do -+ before do -+ `rm -rf test.db` -+ end -+ - def run_script(commands) - raw_output = nil -- IO.popen("./db", "r+") do |pipe| -+ IO.popen("./db test.db", "r+") do |pipe| - commands.each do |command| - pipe.puts command - end -@@ -28,6 +32,27 @@ describe 'database' do - ]) - end - -+ it 'keeps data after closing connection' do -+ result1 = run_script([ -+ "insert 1 user1 person1@example.com", -+ ".exit", -+ ]) -+ expect(result1).to match_array([ -+ "db > Executed.", -+ "db > ", -+ ]) -+ -+ result2 = run_script([ -+ "select", -+ ".exit", -+ ]) -+ expect(result2).to match_array([ -+ "db > (1, user1, person1@example.com)", -+ "Executed.", -+ "db > ", -+ ]) -+ end -+ - it 'prints error message when table is full' do - script = (1..1401).map do |i| - "insert #{i} user#{i} person#{i}@example.com" ``` And the diff to our tests: @@ -581,7 +568,7 @@ And the diff to our tests: @@ -28,6 +32,27 @@ describe 'database' do ]) end - + + it 'keeps data after closing connection' do + result1 = run_script([ + "insert 1 user1 person1@example.com", From 6c61e666e65971cb378702026e76598cafb24e7d Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Wed, 10 Apr 2019 17:15:33 +0300 Subject: [PATCH 36/58] Update total diff in part6.md --- _parts/part6.md | 63 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/_parts/part6.md b/_parts/part6.md index d9fc4c8..2075761 100644 --- a/_parts/part6.md +++ b/_parts/part6.md @@ -124,24 +124,30 @@ Alright, that's it! Like I said, this was a shorter refactor that should help us Here's the complete diff to this part: ```diff +@@ -78,6 +78,13 @@ struct Table_t { }; typedef struct Table_t Table; - + +struct Cursor_t { + Table* table; + uint32_t row_num; -+ bool end_of_table; // Indicates a position one past the last element ++ bool end_of_table; // Indicates a position one past the last element +}; +typedef struct Cursor_t Cursor; + void print_row(Row* row) { - printf("(%d, %s, %s)\n", row->id, row->username, row->email); + printf("(%d, %s, %s)\n", row->id, row->username, row->email); } -@@ -125,14 +132,40 @@ void* get_page(Pager* pager, uint32_t page_num) { - return pager->pages[page_num]; +@@ -126,12 +133,38 @@ void* get_page(Pager* pager, uint32_t page_num) { + return pager->pages[page_num]; } - + -void* row_slot(Table* table, uint32_t row_num) { +- uint32_t page_num = row_num / ROWS_PER_PAGE; +- void *page = get_page(table->pager, page_num); +- uint32_t row_offset = row_num % ROWS_PER_PAGE; +- uint32_t byte_offset = row_offset * ROW_SIZE; +- return page + byte_offset; +Cursor* table_start(Table* table) { + Cursor* cursor = malloc(sizeof(Cursor)); + cursor->table = table; @@ -162,55 +168,50 @@ Here's the complete diff to this part: + +void* cursor_value(Cursor* cursor) { + uint32_t row_num = cursor->row_num; - uint32_t page_num = row_num / ROWS_PER_PAGE; -- void* page = get_page(table->pager, page_num); -+ void* page = get_page(cursor->table->pager, page_num); - uint32_t row_offset = row_num % ROWS_PER_PAGE; - uint32_t byte_offset = row_offset * ROW_SIZE; - return page + byte_offset; - } - ++ uint32_t page_num = row_num / ROWS_PER_PAGE; ++ void *page = get_page(cursor->table->pager, page_num); ++ uint32_t row_offset = row_num % ROWS_PER_PAGE; ++ uint32_t byte_offset = row_offset * ROW_SIZE; ++ return page + byte_offset; ++} ++ +void cursor_advance(Cursor* cursor) { + cursor->row_num += 1; + if (cursor->row_num >= cursor->table->num_rows) { + cursor->end_of_table = true; + } -+} -+ + } + Pager* pager_open(const char* filename) { - int fd = open(filename, - O_RDWR | // Read/Write mode -@@ -315,19 +348,28 @@ ExecuteResult execute_insert(Statement* statement, Table* table) { - } - +@@ -327,19 +360,28 @@ ExecuteResult execute_insert(Statement* statement, Table* table) { + } + Row* row_to_insert = &(statement->row_to_insert); + Cursor* cursor = table_end(table); - + - serialize_row(row_to_insert, row_slot(table, table->num_rows)); + serialize_row(row_to_insert, cursor_value(cursor)); table->num_rows += 1; - + + free(cursor); + return EXECUTE_SUCCESS; } - + ExecuteResult execute_select(Statement* statement, Table* table) { + Cursor* cursor = table_start(table); + Row row; - for (uint32_t i = 0; i < table->num_rows; i++) { -- deserialize_row(row_slot(table, i), &row); +- deserialize_row(row_slot(table, i), &row); + while (!(cursor->end_of_table)) { -+ deserialize_row(cursor_value(cursor), &row); - print_row(&row); -+ cursor_advance(cursor); ++ deserialize_row(cursor_value(cursor), &row); + print_row(&row); ++ cursor_advance(cursor); } + + free(cursor); + return EXECUTE_SUCCESS; } - - -``` \ No newline at end of file +``` From a182bb753f03e330ee199374c6b1a5600bab3955 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Fri, 12 Apr 2019 11:02:19 +0300 Subject: [PATCH 37/58] Update part8.md * Remove inconsistent (char *) cast. * Update diff marks to be consistent with previous revisions. --- _parts/part8.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/_parts/part8.md b/_parts/part8.md index f027b75..1690462 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -93,11 +93,11 @@ The code to access keys, values and metadata all involve pointer arithmetic usin ```diff +uint32_t* leaf_node_num_cells(void* node) { -+ return (char *)node + LEAF_NODE_NUM_CELLS_OFFSET; ++ return node + LEAF_NODE_NUM_CELLS_OFFSET; +} + +void* leaf_node_cell(void* node, uint32_t cell_num) { -+ return (char *)node + LEAF_NODE_HEADER_SIZE + cell_num * LEAF_NODE_CELL_SIZE; ++ return node + LEAF_NODE_HEADER_SIZE + cell_num * LEAF_NODE_CELL_SIZE; +} + +uint32_t* leaf_node_key(void* node, uint32_t cell_num) { @@ -503,8 +503,10 @@ Next time, we'll implement finding a record by primary key, and start storing ro ## Complete Diff ```diff +@@ -62,29 +62,101 @@ const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE; + const uint32_t PAGE_SIZE = 4096; - const uint32_t TABLE_MAX_PAGES = 100; + #define TABLE_MAX_PAGES 100 -const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; -const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; @@ -531,10 +533,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro bool end_of_table; // Indicates a position one past the last element }; typedef struct Cursor_t Cursor; -@@ -88,6 +88,77 @@ void print_row(Row* row) { - printf("(%d, %s, %s)\n", row->id, row->username, row->email); - } - + +enum NodeType_t { NODE_INTERNAL, NODE_LEAF }; +typedef enum NodeType_t NodeType; + @@ -605,11 +604,11 @@ Next time, we'll implement finding a record by primary key, and start storing ro + } +} + - void serialize_row(Row* source, void* destination) { - memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE); - memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE); -@@ -100,6 +171,8 @@ void deserialize_row(void* source, Row* destination) { - memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); + void print_row(Row* row) { + printf("(%d, %s, %s)\n", row->id, row->username, row->email); + } +@@ -101,6 +173,8 @@ void deserialize_row(void *source, Row* destination) { + memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); } +void initialize_leaf_node(void* node) { *leaf_node_num_cells(node) = 0; } @@ -617,7 +616,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro void* get_page(Pager* pager, uint32_t page_num) { if (page_num > TABLE_MAX_PAGES) { printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, -@@ -127,6 +200,10 @@ void* get_page(Pager* pager, uint32_t page_num) { +@@ -128,6 +202,10 @@ void* get_page(Pager* pager, uint32_t page_num) { } pager->pages[page_num] = page; @@ -628,7 +627,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro } return pager->pages[page_num]; -@@ -135,8 +212,12 @@ void* get_page(Pager* pager, uint32_t page_num) { +@@ -136,8 +214,12 @@ void* get_page(Pager* pager, uint32_t page_num) { Cursor* table_start(Table* table) { Cursor* cursor = malloc(sizeof(Cursor)); cursor->table = table; @@ -643,7 +642,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro return cursor; } -@@ -144,24 +225,28 @@ Cursor* table_start(Table* table) { +@@ -145,24 +227,28 @@ Cursor* table_start(Table* table) { Cursor* table_end(Table* table) { Cursor* cursor = malloc(sizeof(Cursor)); cursor->table = table; @@ -680,7 +679,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro cursor->end_of_table = true; } } -@@ -184,6 +269,12 @@ Pager* pager_open(const char* filename) { +@@ -185,6 +271,12 @@ Pager* pager_open(const char* filename) { Pager* pager = malloc(sizeof(Pager)); pager->file_descriptor = fd; pager->file_length = file_length; @@ -694,6 +693,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { pager->pages[i] = NULL; @@ -194,11 +285,15 @@ Pager* pager_open(const char* filename) { +@@ -195,11 +287,16 @@ Pager* pager_open(const char* filename) { Table* db_open(const char* filename) { Pager* pager = pager_open(filename); @@ -712,8 +712,8 @@ Next time, we'll implement finding a record by primary key, and start storing ro return table; } -@@ -228,7 +323,7 @@ void read_input(InputBuffer* input_buffer) { - input_buffer->buffer[bytes_read - 1] = 0; +@@ -234,7 +331,7 @@ void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer); } -void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { @@ -722,6 +722,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro printf("Tried to flush null page\n"); exit(EXIT_FAILURE); @@ -242,7 +337,7 @@ void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { +@@ -249,7 +346,7 @@ void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { } ssize_t bytes_written = @@ -731,6 +732,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro if (bytes_written == -1) { printf("Error writing: %d\n", errno); @@ -252,29 +347,16 @@ void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { +@@ -260,29 +357,16 @@ void pager_flush(Pager* pager, uint32_t page_num, uint32_t size) { void db_close(Table* table) { Pager* pager = table->pager; @@ -762,7 +764,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro int result = close(pager->file_descriptor); if (result == -1) { printf("Error closing db file.\n"); -@@ -294,6 +376,14 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table) { +@@ -305,6 +389,14 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table *table) { if (strcmp(input_buffer->buffer, ".exit") == 0) { db_close(table); exit(EXIT_SUCCESS); @@ -777,7 +779,7 @@ Next time, we'll implement finding a record by primary key, and start storing ro } else { return META_COMMAND_UNRECOGNIZED_COMMAND; } -@@ -342,16 +432,39 @@ PrepareResult prepare_statement(InputBuffer* input_buffer, +@@ -354,16 +446,39 @@ PrepareResult prepare_statement(InputBuffer* input_buffer, return PREPARE_UNRECOGNIZED_STATEMENT; } From b892e447302a473968b81b609422e881a0f4b1a0 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Tue, 16 Apr 2019 16:14:29 +0300 Subject: [PATCH 38/58] Fix erratum in part10 --- _parts/part10.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part10.md b/_parts/part10.md index b59c355..dc237c2 100644 --- a/_parts/part10.md +++ b/_parts/part10.md @@ -377,7 +377,7 @@ with a new recursive function that takes any node, then prints it and its childr + child = *internal_node_child(node, i); + print_tree(pager, child, indentation_level + 1); + -+ indent(indentation_level); ++ indent(indentation_level + 1); + printf("- key %d\n", *internal_node_key(node, i)); + } + child = *internal_node_right_child(node); @@ -420,7 +420,7 @@ Here's a test case for the new printing functionality! + " - 5", + " - 6", + " - 7", -+ "- key 7", ++ " - key 7", + " - leaf (size 7)", + " - 8", + " - 9", From c7074b078532decfead61a04286b93f1a7fab323 Mon Sep 17 00:00:00 2001 From: Konstantinos Demartinos Date: Tue, 16 Apr 2019 16:34:16 +0300 Subject: [PATCH 39/58] Fix small issues * Free table, input_buffer * Handle variably defined arrays at file scope * Include missing header reference --- db.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/db.c b/db.c index 61bc8a5..ec13f75 100644 --- a/db.c +++ b/db.c @@ -4,6 +4,7 @@ #include #include #include +#include #include struct InputBuffer_t { @@ -16,7 +17,6 @@ typedef struct InputBuffer_t InputBuffer; enum ExecuteResult_t { EXECUTE_SUCCESS, EXECUTE_DUPLICATE_KEY, - EXECUTE_TABLE_FULL }; typedef enum ExecuteResult_t ExecuteResult; @@ -38,8 +38,8 @@ typedef enum PrepareResult_t PrepareResult; enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; typedef enum StatementType_t StatementType; -const uint32_t COLUMN_USERNAME_SIZE = 32; -const uint32_t COLUMN_EMAIL_SIZE = 255; +#define COLUMN_USERNAME_SIZE 32 +#define COLUMN_EMAIL_SIZE 255 struct Row_t { uint32_t id; char username[COLUMN_USERNAME_SIZE + 1]; @@ -64,7 +64,7 @@ const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE; const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE; const uint32_t PAGE_SIZE = 4096; -const uint32_t TABLE_MAX_PAGES = 100; +#define TABLE_MAX_PAGES 100 struct Pager_t { int file_descriptor; @@ -536,6 +536,11 @@ void read_input(InputBuffer* input_buffer) { input_buffer->buffer[bytes_read - 1] = 0; } +void close_input_buffer(InputBuffer* input_buffer) { + free(input_buffer->buffer); + free(input_buffer); +} + void pager_flush(Pager* pager, uint32_t page_num) { if (pager->pages[page_num] == NULL) { printf("Tried to flush null page\n"); @@ -583,10 +588,12 @@ void db_close(Table* table) { } } free(pager); + free(table); } MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table) { if (strcmp(input_buffer->buffer, ".exit") == 0) { + close_input_buffer(input_buffer); db_close(table); exit(EXIT_SUCCESS); } else if (strcmp(input_buffer->buffer, ".btree") == 0) { @@ -904,9 +911,6 @@ int main(int argc, char* argv[]) { case (EXECUTE_DUPLICATE_KEY): printf("Error: Duplicate key.\n"); break; - case (EXECUTE_TABLE_FULL): - printf("Error: Table full.\n"); - break; } } } From ea1152c61ca7ff76eac1882fc66821ae0dde1bb7 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Wed, 17 Apr 2019 08:49:32 +0300 Subject: [PATCH 40/58] Fix error in part3.md Co-Authored-By: kodemartin --- _parts/part3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part3.md b/_parts/part3.md index 8ceca49..f635c5e 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -132,7 +132,7 @@ Speaking of which, here is how we figure out where to read/write in memory for a +void* row_slot(Table* table, uint32_t row_num) { + uint32_t page_num = row_num / ROWS_PER_PAGE; + void* page = table->pages[page_num]; -+ if (page = NULL) { ++ if (page == NULL) { + // Allocate memory only when we try to access page + page = table->pages[page_num] = malloc(PAGE_SIZE); + } From e2acac365cd9b4d6480a4c6da8691423c3d24a9e Mon Sep 17 00:00:00 2001 From: shaqsnake Date: Mon, 29 Apr 2019 17:08:33 +0800 Subject: [PATCH 41/58] Fixed error in part3.md. Delete '=' at line #47 and #48. --- _parts/part3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index f635c5e..f5a9715 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -44,8 +44,8 @@ That means we need to upgrade our `prepare_statement` function to parse argument We store those parsed arguments into a new `Row` data structure inside the statement object: ```diff -+#define COLUMN_USERNAME_SIZE = 32; -+#define COLUMN_EMAIL_SIZE = 255; ++#define COLUMN_USERNAME_SIZE 32; ++#define COLUMN_EMAIL_SIZE 255; +struct Row_t { + uint32_t id; + char username[COLUMN_USERNAME_SIZE]; From 180757039eeac528266b95f486b44cef07a33228 Mon Sep 17 00:00:00 2001 From: shaqsnake Date: Mon, 29 Apr 2019 17:50:25 +0800 Subject: [PATCH 42/58] Fixed error in part3.md And the ';' should be removed too. --- _parts/part3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index f5a9715..205c4c7 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -44,8 +44,8 @@ That means we need to upgrade our `prepare_statement` function to parse argument We store those parsed arguments into a new `Row` data structure inside the statement object: ```diff -+#define COLUMN_USERNAME_SIZE 32; -+#define COLUMN_EMAIL_SIZE 255; ++#define COLUMN_USERNAME_SIZE 32 ++#define COLUMN_EMAIL_SIZE 255 +struct Row_t { + uint32_t id; + char username[COLUMN_USERNAME_SIZE]; From d423c3b7245fee4a72a6bf9c2955e850017a898a Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Fri, 21 Jun 2019 21:11:21 -0700 Subject: [PATCH 43/58] Simpler typedef --- _parts/part1.md | 10 ++++------ _parts/part2.md | 35 ++++++++++++-------------------- _parts/part3.md | 53 ++++++++++++++++++++----------------------------- _parts/part4.md | 14 ++++++------- _parts/part5.md | 19 ++++++++---------- _parts/part6.md | 15 ++++++-------- _parts/part8.md | 36 +++++++++++++-------------------- db.c | 51 +++++++++++++++++++---------------------------- 8 files changed, 92 insertions(+), 141 deletions(-) diff --git a/_parts/part1.md b/_parts/part1.md index 2535dd6..1d9059c 100644 --- a/_parts/part1.md +++ b/_parts/part1.md @@ -83,12 +83,11 @@ int main(int argc, char* argv[]) { We'll define `InputBuffer` as a small wrapper around the state we need to store to interact with [getline()](http://man7.org/linux/man-pages/man3/getline.3.html). (More on that in a minute) ```c -struct InputBuffer_t { +typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; -}; -typedef struct InputBuffer_t InputBuffer; +} InputBuffer; InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); @@ -178,12 +177,11 @@ Alright, we've got a working REPL. In the next part, we'll start developing our #include #include -struct InputBuffer_t { +typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; -}; -typedef struct InputBuffer_t InputBuffer; +} InputBuffer; InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); diff --git a/_parts/part2.md b/_parts/part2.md index 225120a..4b16b7c 100644 --- a/_parts/part2.md +++ b/_parts/part2.md @@ -61,14 +61,12 @@ Lastly, we pass the prepared statement to `execute_statement`. This function wil Notice that two of our new functions return enums indicating success or failure: ```c -enum MetaCommandResult_t { +typedef enum { META_COMMAND_SUCCESS, META_COMMAND_UNRECOGNIZED_COMMAND -}; -typedef enum MetaCommandResult_t MetaCommandResult; +} MetaCommandResult; -enum PrepareResult_t { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT }; -typedef enum PrepareResult_t PrepareResult; +typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult; ``` "Unrecognized statement"? That seems a bit like an exception. But [exceptions are bad](https://www.youtube.com/watch?v=EVhCUSgNbzo) (and C doesn't even support them), so I'm using enum result codes wherever practical. The C compiler will complain if my switch statement doesn't handle a member of the enum, so we can feel a little more confident we handle every result of a function. Expect more result codes to be added in the future. @@ -88,13 +86,11 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer) { Our "prepared statement" right now just contains an enum with two possible values. It will contain more data as we allow parameters in statements: ```c -enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; -typedef enum StatementType_t StatementType; +typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType; -struct Statement_t { +typedef struct { StatementType type; -}; -typedef struct Statement_t Statement; +} Statement; ``` `prepare_statement` (our "SQL Compiler") does not understand SQL right now. In fact, it only understands two words: @@ -153,25 +149,20 @@ The skeleton of our database is taking shape... wouldn't it be nice if it stored ```diff @@ -10,6 +10,23 @@ struct InputBuffer_t { - }; - typedef struct InputBuffer_t InputBuffer; + } InputBuffer; -+enum MetaCommandResult_t { ++typedef enum { + META_COMMAND_SUCCESS, + META_COMMAND_UNRECOGNIZED_COMMAND -+}; -+typedef enum MetaCommandResult_t MetaCommandResult; ++} MetaCommandResult; + -+enum PrepareResult_t { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT }; -+typedef enum PrepareResult_t PrepareResult; ++typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult; + -+enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; -+typedef enum StatementType_t StatementType; ++typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType; + -+struct Statement_t { ++typedef struct { + StatementType type; -+}; -+typedef struct Statement_t Statement; ++} Statement; + InputBuffer* new_input_buffer() { InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); diff --git a/_parts/part3.md b/_parts/part3.md index 205c4c7..cfb0d1e 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -46,18 +46,16 @@ We store those parsed arguments into a new `Row` data structure inside the state ```diff +#define COLUMN_USERNAME_SIZE 32 +#define COLUMN_EMAIL_SIZE 255 -+struct Row_t { ++typedef struct { + uint32_t id; + char username[COLUMN_USERNAME_SIZE]; + char email[COLUMN_EMAIL_SIZE]; -+}; -+typedef struct Row_t Row; ++} Row; + - struct Statement_t { + typedef struct { StatementType type; + Row row_to_insert; // only used by insert statement - }; - typedef struct Statement_t Statement; + } Statement; ``` Now we need to copy that data into some data structure representing the table. SQLite uses a B-tree for fast lookups, inserts and deletes. We'll start with something simpler. Like a B-tree, it will group rows into pages, but instead of arranging those pages as a tree it will arrange them as an array. @@ -114,11 +112,10 @@ Next, a `Table` structure that points to pages of rows and keeps track of how ma +const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + -+struct Table_t { ++typedef struct { + uint32_t num_rows; + void* pages[TABLE_MAX_PAGES]; -+}; -+typedef struct Table_t Table; ++} Table; ``` I'm making our page size 4 kilobytes because it's the same size as a page used in the virtual memory systems of most computer architectures. This means one page in our database corresponds to one page used by the operating system. The operating system will move pages in and out of memory as whole units instead of breaking them up. @@ -263,45 +260,38 @@ We'll address those issues in the next part. For now, here's the complete diff f #include +#include - struct InputBuffer_t { + typedef struct { char* buffer; -@@ -10,6 +11,105 @@ struct InputBuffer_t { - }; - typedef struct InputBuffer_t InputBuffer; +@@ -10,6 +11,105 @@ typedef struct { + } InputBuffer; -+enum ExecuteResult_t { EXECUTE_SUCCESS, EXECUTE_TABLE_FULL }; -+typedef enum ExecuteResult_t ExecuteResult; ++typedef enum { EXECUTE_SUCCESS, EXECUTE_TABLE_FULL } ExecuteResult; + -+enum MetaCommandResult_t { ++typedef enum { + META_COMMAND_SUCCESS, + META_COMMAND_UNRECOGNIZED_COMMAND -+}; -+typedef enum MetaCommandResult_t MetaCommandResult; ++} MetaCommandResult; + -+enum PrepareResult_t { ++typedef enum { + PREPARE_SUCCESS, + PREPARE_SYNTAX_ERROR, + PREPARE_UNRECOGNIZED_STATEMENT -+ }; -+typedef enum PrepareResult_t PrepareResult; ++ } PrepareResult; + -+enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; -+typedef enum StatementType_t StatementType; ++typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType; + +#define COLUMN_USERNAME_SIZE 32 +#define COLUMN_EMAIL_SIZE 255 -+struct Row_t { ++typedef struct { + uint32_t id; + char username[COLUMN_USERNAME_SIZE]; + char email[COLUMN_EMAIL_SIZE]; -+}; -+typedef struct Row_t Row; ++} Row; + -+struct Statement_t { ++typedef struct { + StatementType type; + Row row_to_insert; //only used by insert statement -+}; -+typedef struct Statement_t Statement; ++} Statement; + +#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute) + @@ -318,11 +308,10 @@ We'll address those issues in the next part. For now, here's the complete diff f +const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; +const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; + -+struct Table_t { ++typedef struct { + uint32_t num_rows; + void* pages[TABLE_MAX_PAGES]; -+}; -+typedef struct Table_t Table; ++} Table; + +void print_row(Row* row) { + printf("(%d, %s, %s)\n", row->id, row->username, row->email); diff --git a/_parts/part4.md b/_parts/part4.md index 1227103..00a462d 100644 --- a/_parts/part4.md +++ b/_parts/part4.md @@ -121,14 +121,13 @@ db > What's going on? If you take a look at our definition of a Row, we allocate exactly 32 bytes for username and exactly 255 bytes for email. But [C strings](http://www.cprogramming.com/tutorial/c/lesson9.html) are supposed to end with a null character, which we didn't allocate space for. The solution is to allocate one additional byte: ```diff const uint32_t COLUMN_EMAIL_SIZE = 255; - struct Row_t { + typedef struct { uint32_t id; - char username[COLUMN_USERNAME_SIZE]; - char email[COLUMN_EMAIL_SIZE]; + char username[COLUMN_USERNAME_SIZE + 1]; + char email[COLUMN_EMAIL_SIZE + 1]; - }; - typedef struct Row_t Row; + } Row; ``` And indeed that fixes it: @@ -305,7 +304,7 @@ It's gonna be great. Here's the complete diff for this part: ```diff -@@ -22,6 +22,8 @@ typedef enum MetaCommandResult_t MetaCommandResult; +@@ -22,6 +22,8 @@ enum PrepareResult_t { PREPARE_SUCCESS, @@ -314,16 +313,15 @@ Here's the complete diff for this part: PREPARE_SYNTAX_ERROR, PREPARE_UNRECOGNIZED_STATEMENT }; -@@ -34,8 +36,8 @@ typedef enum StatementType_t StatementType; +@@ -34,8 +36,8 @@ #define COLUMN_EMAIL_SIZE 255 - struct Row_t { + typedef struct { uint32_t id; - char username[COLUMN_USERNAME_SIZE]; - char email[COLUMN_EMAIL_SIZE]; + char username[COLUMN_USERNAME_SIZE + 1]; + char email[COLUMN_EMAIL_SIZE + 1]; - }; - typedef struct Row_t Row; + } Row; @@ -150,18 +152,40 @@ MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table *table) { } diff --git a/_parts/part5.md b/_parts/part5.md index 8603f84..497f3d0 100644 --- a/_parts/part5.md +++ b/_parts/part5.md @@ -40,18 +40,17 @@ To make this easier, we're going to make an abstraction called the pager. We ask The Pager accesses the page cache and the file. The Table object makes requests for pages through the pager: ```diff -+struct Pager_t { ++typedef struct { + int file_descriptor; + uint32_t file_length; + void* pages[TABLE_MAX_PAGES]; -+}; -+typedef struct Pager_t Pager; ++} Pager; + - struct Table_t { + typedef struct { - void* pages[TABLE_MAX_PAGES]; + Pager* pager; uint32_t num_rows; - }; + } Table; ``` I'm renaming `new_table()` to `db_open()` because it now has the effect of opening a connection to the database. By opening a connection, I mean: @@ -336,19 +335,17 @@ Until then! const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; -+struct Pager_t { ++typedef struct { + int file_descriptor; + uint32_t file_length; + void* pages[TABLE_MAX_PAGES]; -+}; -+typedef struct Pager_t Pager; ++} Pager; + - struct Table_t { + typedef struct { uint32_t num_rows; - void* pages[TABLE_MAX_PAGES]; + Pager* pager; - }; - typedef struct Table_t Table; + } Table; @@ -84,32 +94,81 @@ void deserialize_row(void *source, Row* destination) { memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE); diff --git a/_parts/part6.md b/_parts/part6.md index 2075761..ee3da27 100644 --- a/_parts/part6.md +++ b/_parts/part6.md @@ -21,12 +21,11 @@ Those are the behaviors we're going to implement now. Later, we will also want t Without further ado, here's the `Cursor` type: ```diff -+struct Cursor_t { ++typedef struct { + Table* table; + uint32_t row_num; + bool end_of_table; // Indicates a position one past the last element -+}; -+typedef struct Cursor_t Cursor; ++} Cursor; ``` Given our current table data structure, all you need to identify a location in a table is the row number. @@ -124,16 +123,14 @@ Alright, that's it! Like I said, this was a shorter refactor that should help us Here's the complete diff to this part: ```diff -@@ -78,6 +78,13 @@ struct Table_t { - }; - typedef struct Table_t Table; +@@ -78,6 +78,13 @@ struct { + } Table; -+struct Cursor_t { ++typedef struct { + Table* table; + uint32_t row_num; + bool end_of_table; // Indicates a position one past the last element -+}; -+typedef struct Cursor_t Cursor; ++} Cursor; + void print_row(Row* row) { printf("(%d, %s, %s)\n", row->id, row->username, row->email); diff --git a/_parts/part8.md b/_parts/part8.md index 1690462..1228a01 100644 --- a/_parts/part8.md +++ b/_parts/part8.md @@ -26,8 +26,7 @@ Instead, we're going with a tree structure. Each node in the tree can contain a Leaf nodes and internal nodes have different layouts. Let's make an enum to keep track of node type: ```diff -+enum NodeType_t { NODE_INTERNAL, NODE_LEAF }; -+typedef enum NodeType_t NodeType; ++typedef enum { NODE_INTERNAL, NODE_LEAF } NodeType; ``` Each node will correspond to one page. Internal nodes will point to their children by storing the page number that stores the child. The btree asks the pager for a particular page number and gets back a pointer into the page cache. Pages are stored in the database file one after the other in order of page number. @@ -175,20 +174,18 @@ Now it makes more sense to store the number of pages in our database rather than -const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; -const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; - struct Pager_t { + typedef struct { int file_descriptor; uint32_t file_length; + uint32_t num_pages; void* pages[TABLE_MAX_PAGES]; - }; - typedef struct Pager_t Pager; + } Pager; - struct Table_t { + typedef struct { Pager* pager; - uint32_t num_rows; + uint32_t root_page_num; - }; - typedef struct Table_t Table; + } Table; ``` ```diff @@ -226,14 +223,13 @@ Now it makes more sense to store the number of pages in our database rather than A cursor represents a position in the table. When our table was a simple array of rows, we could access a row given just the row number. Now that it's a tree, we identify a position by the page number of the node, and the cell number within that node. ```diff - struct Cursor_t { + typedef struct { Table* table; - uint32_t row_num; + uint32_t page_num; + uint32_t cell_num; bool end_of_table; // Indicates a position one past the last element - }; - typedef struct Cursor_t Cursor; + } Cursor; ``` ```diff @@ -510,32 +506,28 @@ Next time, we'll implement finding a record by primary key, and start storing ro -const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE; -const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES; - struct Pager_t { + typedef struct { int file_descriptor; uint32_t file_length; + uint32_t num_pages; void* pages[TABLE_MAX_PAGES]; - }; - typedef struct Pager_t Pager; + } Pager; - struct Table_t { + typedef struct { Pager* pager; - uint32_t num_rows; + uint32_t root_page_num; - }; - typedef struct Table_t Table; + } Table; - struct Cursor_t { + typedef struct { Table* table; - uint32_t row_num; + uint32_t page_num; + uint32_t cell_num; bool end_of_table; // Indicates a position one past the last element - }; - typedef struct Cursor_t Cursor; + } Cursor; -+enum NodeType_t { NODE_INTERNAL, NODE_LEAF }; -+typedef enum NodeType_t NodeType; ++typedef enum { NODE_INTERNAL, NODE_LEAF } NodeType; + +/* + * Common Node Header Layout diff --git a/db.c b/db.c index ec13f75..f58aad7 100644 --- a/db.c +++ b/db.c @@ -7,51 +7,44 @@ #include #include -struct InputBuffer_t { +typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; -}; -typedef struct InputBuffer_t InputBuffer; +} InputBuffer; -enum ExecuteResult_t { +typedef enum { EXECUTE_SUCCESS, EXECUTE_DUPLICATE_KEY, -}; -typedef enum ExecuteResult_t ExecuteResult; +} ExecuteResult; -enum MetaCommandResult_t { +typedef enum { META_COMMAND_SUCCESS, META_COMMAND_UNRECOGNIZED_COMMAND -}; -typedef enum MetaCommandResult_t MetaCommandResult; +} MetaCommandResult; -enum PrepareResult_t { +typedef enum { PREPARE_SUCCESS, PREPARE_NEGATIVE_ID, PREPARE_STRING_TOO_LONG, PREPARE_SYNTAX_ERROR, PREPARE_UNRECOGNIZED_STATEMENT -}; -typedef enum PrepareResult_t PrepareResult; +} PrepareResult; -enum StatementType_t { STATEMENT_INSERT, STATEMENT_SELECT }; -typedef enum StatementType_t StatementType; +typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType; #define COLUMN_USERNAME_SIZE 32 #define COLUMN_EMAIL_SIZE 255 -struct Row_t { +typedef struct { uint32_t id; char username[COLUMN_USERNAME_SIZE + 1]; char email[COLUMN_EMAIL_SIZE + 1]; -}; -typedef struct Row_t Row; +} Row; -struct Statement_t { +typedef struct { StatementType type; Row row_to_insert; // only used by insert statement -}; -typedef struct Statement_t Statement; +} Statement; #define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute) @@ -66,34 +59,30 @@ const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE; const uint32_t PAGE_SIZE = 4096; #define TABLE_MAX_PAGES 100 -struct Pager_t { +typedef struct { int file_descriptor; uint32_t file_length; uint32_t num_pages; void* pages[TABLE_MAX_PAGES]; -}; -typedef struct Pager_t Pager; +} Pager; -struct Table_t { +typedef struct { Pager* pager; uint32_t root_page_num; -}; -typedef struct Table_t Table; +} Table; -struct Cursor_t { +typedef struct { Table* table; uint32_t page_num; uint32_t cell_num; bool end_of_table; // Indicates a position one past the last element -}; -typedef struct Cursor_t Cursor; +} Cursor; void print_row(Row* row) { printf("(%d, %s, %s)\n", row->id, row->username, row->email); } -enum NodeType_t { NODE_INTERNAL, NODE_LEAF }; -typedef enum NodeType_t NodeType; +typedef enum { NODE_INTERNAL, NODE_LEAF } NodeType; /* * Common Node Header Layout From 09f009c61a0735afe4e62aca1ec3e85dba2b92b3 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Fri, 21 Jun 2019 21:13:27 -0700 Subject: [PATCH 44/58] Fix formatting --- db.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db.c b/db.c index f58aad7..4250a91 100644 --- a/db.c +++ b/db.c @@ -1,10 +1,10 @@ #include #include #include +#include #include #include #include -#include #include typedef struct { @@ -526,8 +526,8 @@ void read_input(InputBuffer* input_buffer) { } void close_input_buffer(InputBuffer* input_buffer) { - free(input_buffer->buffer); - free(input_buffer); + free(input_buffer->buffer); + free(input_buffer); } void pager_flush(Pager* pager, uint32_t page_num) { @@ -809,7 +809,7 @@ ExecuteResult execute_insert(Statement* statement, Table* table) { uint32_t key_to_insert = row_to_insert->id; Cursor* cursor = table_find(table, key_to_insert); - void *node = get_page(table->pager, cursor->page_num); + void* node = get_page(table->pager, cursor->page_num); uint32_t num_cells = *leaf_node_num_cells(node); if (cursor->cell_num < num_cells) { From 2248a7febf3c85b4354389114c6ca65142e35d3e Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Fri, 21 Jun 2019 21:16:26 -0700 Subject: [PATCH 45/58] Upgrade nokogiri --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bb3d412..cca5cd6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,15 +199,15 @@ GEM rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) mercenary (0.3.6) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minima (2.5.0) jekyll (~> 3.5) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.11.3) multipart-post (2.0.0) - nokogiri (1.8.4) - mini_portile2 (~> 2.3.0) + nokogiri (1.10.3) + mini_portile2 (~> 2.4.0) octokit (4.12.0) sawyer (~> 0.8.0, >= 0.5.3) pathutil (0.16.1) From bf7c20265f4fc3b320b8b2d3330943633c290282 Mon Sep 17 00:00:00 2001 From: stardustman Date: Sun, 14 Jul 2019 12:47:08 +0800 Subject: [PATCH 46/58] fix malloc() return value cast to pointer of InputBuffer --- _parts/part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part1.md b/_parts/part1.md index 1d9059c..7b2a365 100644 --- a/_parts/part1.md +++ b/_parts/part1.md @@ -90,7 +90,7 @@ typedef struct { } InputBuffer; InputBuffer* new_input_buffer() { - InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); + InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; input_buffer->buffer_length = 0; input_buffer->input_length = 0; From 7c10dafcecd73f3404036a109c7a88aada56b253 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2019 05:16:29 +0000 Subject: [PATCH 47/58] Bump nokogiri from 1.10.3 to 1.10.4 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.3 to 1.10.4. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.3...v1.10.4) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cca5cd6..1dde8d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,7 +206,7 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.11.3) multipart-post (2.0.0) - nokogiri (1.10.3) + nokogiri (1.10.4) mini_portile2 (~> 2.4.0) octokit (4.12.0) sawyer (~> 0.8.0, >= 0.5.3) From feb73f54ae906727ed427075c07da13dd3f44a04 Mon Sep 17 00:00:00 2001 From: ohbarye Date: Thu, 26 Dec 2019 23:31:01 +0900 Subject: [PATCH 48/58] Fix typo: retreives -> retrieves --- _parts/part4.md | 4 ++-- spec/main_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_parts/part4.md b/_parts/part4.md index 00a462d..9a93bf2 100644 --- a/_parts/part4.md +++ b/_parts/part4.md @@ -26,7 +26,7 @@ describe 'database' do raw_output.split("\n") end - it 'inserts and retreives a row' do + it 'inserts and retrieves a row' do result = run_script([ "insert 1 user1 person1@example.com", "select", @@ -404,7 +404,7 @@ And we added tests: + raw_output.split("\n") + end + -+ it 'inserts and retreives a row' do ++ it 'inserts and retrieves a row' do + result = run_script([ + "insert 1 user1 person1@example.com", + "select", diff --git a/spec/main_spec.rb b/spec/main_spec.rb index 4dbb826..d264105 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -22,7 +22,7 @@ def run_script(commands) raw_output.split("\n") end - it 'inserts and retreives a row' do + it 'inserts and retrieves a row' do result = run_script([ "insert 1 user1 person1@example.com", "select", From fca2ab2ebf0fc09bf9db5d4a1bfb10878a7d4ebf Mon Sep 17 00:00:00 2001 From: ohbarye Date: Thu, 26 Dec 2019 23:39:15 +0900 Subject: [PATCH 49/58] Fix typo: duplicated the --- _parts/part4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part4.md b/_parts/part4.md index 9a93bf2..c4df85e 100644 --- a/_parts/part4.md +++ b/_parts/part4.md @@ -219,7 +219,7 @@ I'm going to use [strtok()](http://www.cplusplus.com/reference/cstring/strtok/) } ``` -Calling `strtok` successively on the the input buffer breaks it into substrings by inserting a null character whenever it reaches a delimiter (space, in our case). It returns a pointer to the start of the substring. +Calling `strtok` successively on the input buffer breaks it into substrings by inserting a null character whenever it reaches a delimiter (space, in our case). It returns a pointer to the start of the substring. We can call [strlen()](http://www.cplusplus.com/reference/cstring/strlen/) on each text value to see if it's too long. From f31f266889ea0902202510bc192e3456d152964d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2020 20:20:17 +0000 Subject: [PATCH 50/58] Bump nokogiri from 1.10.4 to 1.10.8 Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.4 to 1.10.8. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/master/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.4...v1.10.8) Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1dde8d2..a279432 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,7 +206,7 @@ GEM jekyll-seo-tag (~> 2.1) minitest (5.11.3) multipart-post (2.0.0) - nokogiri (1.10.4) + nokogiri (1.10.8) mini_portile2 (~> 2.4.0) octokit (4.12.0) sawyer (~> 0.8.0, >= 0.5.3) From 622f1c8b6e93dee7c0b17d8803689792076a4b2e Mon Sep 17 00:00:00 2001 From: Dennis Kovshov <6663601+denniskovshov@users.noreply.github.com> Date: Thu, 1 Jul 2021 20:53:32 -0400 Subject: [PATCH 51/58] added missing casting for malloc --- _parts/part3.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_parts/part3.md b/_parts/part3.md index cfb0d1e..f2c4a61 100644 --- a/_parts/part3.md +++ b/_parts/part3.md @@ -183,7 +183,7 @@ memory release function and handle a few more error cases: ```diff + Table* new_table() { -+ Table* table = malloc(sizeof(Table)); ++ Table* table = (Table*)malloc(sizeof(Table)); + table->num_rows = 0; + for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { + table->pages[i] = NULL; @@ -342,7 +342,7 @@ We'll address those issues in the next part. For now, here's the complete diff f +} + +Table* new_table() { -+ Table* table = malloc(sizeof(Table)); ++ Table* table = (Table*)malloc(sizeof(Table)); + table->num_rows = 0; + for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) { + table->pages[i] = NULL; @@ -358,7 +358,7 @@ We'll address those issues in the next part. For now, here's the complete diff f +} + InputBuffer* new_input_buffer() { - InputBuffer* input_buffer = malloc(sizeof(InputBuffer)); + InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer)); input_buffer->buffer = NULL; @@ -40,17 +140,105 @@ void close_input_buffer(InputBuffer* input_buffer) { free(input_buffer); From 9c15e71cd27487ca3994aa6b48b6566736711ac3 Mon Sep 17 00:00:00 2001 From: xnacly Date: Thu, 17 Mar 2022 12:47:06 +0100 Subject: [PATCH 52/58] Removed unavailable YouTube video --- _parts/part2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part2.md b/_parts/part2.md index 4b16b7c..79aa76f 100644 --- a/_parts/part2.md +++ b/_parts/part2.md @@ -69,7 +69,7 @@ typedef enum { typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult; ``` -"Unrecognized statement"? That seems a bit like an exception. But [exceptions are bad](https://www.youtube.com/watch?v=EVhCUSgNbzo) (and C doesn't even support them), so I'm using enum result codes wherever practical. The C compiler will complain if my switch statement doesn't handle a member of the enum, so we can feel a little more confident we handle every result of a function. Expect more result codes to be added in the future. +"Unrecognized statement"? That seems a bit like an exception. But exceptions are bad (and C doesn't even support them), so I'm using enum result codes wherever practical. The C compiler will complain if my switch statement doesn't handle a member of the enum, so we can feel a little more confident we handle every result of a function. Expect more result codes to be added in the future. `do_meta_command` is just a wrapper for existing functionality that leaves room for more commands: From 7aa715f46b2fd68f4adcfa74514dba7f67ab64f8 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Thu, 17 Mar 2022 12:02:01 -0400 Subject: [PATCH 53/58] Update _parts/part2.md --- _parts/part2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_parts/part2.md b/_parts/part2.md index 79aa76f..48d58ab 100644 --- a/_parts/part2.md +++ b/_parts/part2.md @@ -69,7 +69,7 @@ typedef enum { typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult; ``` -"Unrecognized statement"? That seems a bit like an exception. But exceptions are bad (and C doesn't even support them), so I'm using enum result codes wherever practical. The C compiler will complain if my switch statement doesn't handle a member of the enum, so we can feel a little more confident we handle every result of a function. Expect more result codes to be added in the future. +"Unrecognized statement"? That seems a bit like an exception. I prefer not to use exceptions (and C doesn't even support them), so I'm using enum result codes wherever practical. The C compiler will complain if my switch statement doesn't handle a member of the enum, so we can feel a little more confident we handle every result of a function. Expect more result codes to be added in the future. `do_meta_command` is just a wrapper for existing functionality that leaves room for more commands: From 93f67fa210cdd437c414bbc625b3784a17e95219 Mon Sep 17 00:00:00 2001 From: Luke Hawthorne Date: Fri, 19 May 2023 21:47:39 -0700 Subject: [PATCH 54/58] Implement splitting internal nodes; add test case --- db.c | 223 ++++++++++++++++++++++++++++++++++++++-------- spec/main_spec.rb | 159 ++++++++++++++++++++++++++++++++- 2 files changed, 342 insertions(+), 40 deletions(-) diff --git a/db.c b/db.c index 4250a91..ce3a590 100644 --- a/db.c +++ b/db.c @@ -57,7 +57,9 @@ const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE; const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE; const uint32_t PAGE_SIZE = 4096; -#define TABLE_MAX_PAGES 100 +#define TABLE_MAX_PAGES 400 + +#define INVALID_PAGE_NUM UINT32_MAX typedef struct { int file_descriptor; @@ -116,7 +118,7 @@ const uint32_t INTERNAL_NODE_CHILD_SIZE = sizeof(uint32_t); const uint32_t INTERNAL_NODE_CELL_SIZE = INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE; /* Keep this small for testing */ -const uint32_t INTERNAL_NODE_MAX_CELLS = 3; +const uint32_t INTERNAL_NODE_MAX_KEYS = 3; /* * Leaf Node Header Layout @@ -186,9 +188,19 @@ uint32_t* internal_node_child(void* node, uint32_t child_num) { printf("Tried to access child_num %d > num_keys %d\n", child_num, num_keys); exit(EXIT_FAILURE); } else if (child_num == num_keys) { - return internal_node_right_child(node); + uint32_t* right_child = internal_node_right_child(node); + if (*right_child == INVALID_PAGE_NUM) { + printf("Tried to access right child of node, but was invalid page\n"); + exit(EXIT_FAILURE); + } + return right_child; } else { - return internal_node_cell(node, child_num); + uint32_t* child = internal_node_cell(node, child_num); + if (*child == INVALID_PAGE_NUM) { + printf("Tried to access child %d of node, but was invalid page\n", child_num); + exit(EXIT_FAILURE); + } + return child; } } @@ -216,24 +228,6 @@ void* leaf_node_value(void* node, uint32_t cell_num) { return leaf_node_cell(node, cell_num) + LEAF_NODE_KEY_SIZE; } -uint32_t get_node_max_key(void* node) { - switch (get_node_type(node)) { - case NODE_INTERNAL: - return *internal_node_key(node, *internal_node_num_keys(node) - 1); - case NODE_LEAF: - return *leaf_node_key(node, *leaf_node_num_cells(node) - 1); - } -} - -void print_constants() { - printf("ROW_SIZE: %d\n", ROW_SIZE); - printf("COMMON_NODE_HEADER_SIZE: %d\n", COMMON_NODE_HEADER_SIZE); - printf("LEAF_NODE_HEADER_SIZE: %d\n", LEAF_NODE_HEADER_SIZE); - printf("LEAF_NODE_CELL_SIZE: %d\n", LEAF_NODE_CELL_SIZE); - printf("LEAF_NODE_SPACE_FOR_CELLS: %d\n", LEAF_NODE_SPACE_FOR_CELLS); - printf("LEAF_NODE_MAX_CELLS: %d\n", LEAF_NODE_MAX_CELLS); -} - void* get_page(Pager* pager, uint32_t page_num) { if (page_num > TABLE_MAX_PAGES) { printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, @@ -270,6 +264,23 @@ void* get_page(Pager* pager, uint32_t page_num) { return pager->pages[page_num]; } +uint32_t get_node_max_key(Pager* pager, void* node) { + if (get_node_type(node) == NODE_LEAF) { + return *leaf_node_key(node, *leaf_node_num_cells(node) - 1); + } + void* right_child = get_page(pager,*internal_node_right_child(node)); + return get_node_max_key(pager, right_child); +} + +void print_constants() { + printf("ROW_SIZE: %d\n", ROW_SIZE); + printf("COMMON_NODE_HEADER_SIZE: %d\n", COMMON_NODE_HEADER_SIZE); + printf("LEAF_NODE_HEADER_SIZE: %d\n", LEAF_NODE_HEADER_SIZE); + printf("LEAF_NODE_CELL_SIZE: %d\n", LEAF_NODE_CELL_SIZE); + printf("LEAF_NODE_SPACE_FOR_CELLS: %d\n", LEAF_NODE_SPACE_FOR_CELLS); + printf("LEAF_NODE_MAX_CELLS: %d\n", LEAF_NODE_MAX_CELLS); +} + void indent(uint32_t level) { for (uint32_t i = 0; i < level; i++) { printf(" "); @@ -294,15 +305,17 @@ void print_tree(Pager* pager, uint32_t page_num, uint32_t indentation_level) { num_keys = *internal_node_num_keys(node); indent(indentation_level); printf("- internal (size %d)\n", num_keys); - for (uint32_t i = 0; i < num_keys; i++) { - child = *internal_node_child(node, i); + if (num_keys > 0) { + for (uint32_t i = 0; i < num_keys; i++) { + child = *internal_node_child(node, i); + print_tree(pager, child, indentation_level + 1); + + indent(indentation_level + 1); + printf("- key %d\n", *internal_node_key(node, i)); + } + child = *internal_node_right_child(node); print_tree(pager, child, indentation_level + 1); - - indent(indentation_level + 1); - printf("- key %d\n", *internal_node_key(node, i)); } - child = *internal_node_right_child(node); - print_tree(pager, child, indentation_level + 1); break; } } @@ -330,6 +343,12 @@ void initialize_internal_node(void* node) { set_node_type(node, NODE_INTERNAL); set_node_root(node, false); *internal_node_num_keys(node) = 0; + /* + Necessary because the root page number is 0; by not initializing an internal + node's right child to an invalid page number when initializing the node, we may + end up with 0 as the node's right child, which makes the node a parent of the root + */ + *internal_node_right_child(node) = INVALID_PAGE_NUM; } Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key) { @@ -661,22 +680,40 @@ void create_new_root(Table* table, uint32_t right_child_page_num) { uint32_t left_child_page_num = get_unused_page_num(table->pager); void* left_child = get_page(table->pager, left_child_page_num); + if (get_node_type(root) == NODE_INTERNAL) { + initialize_internal_node(right_child); + initialize_internal_node(left_child); + } + /* Left child has data copied from old root */ memcpy(left_child, root, PAGE_SIZE); set_node_root(left_child, false); + if (get_node_type(left_child) == NODE_INTERNAL) { + void* child; + for (int i = 0; i < *internal_node_num_keys(left_child); i++) { + child = get_page(table->pager, *internal_node_child(left_child,i)); + *node_parent(child) = left_child_page_num; + } + child = get_page(table->pager, *internal_node_right_child(left_child)); + *node_parent(child) = left_child_page_num; + } + /* Root node is a new internal node with one key and two children */ initialize_internal_node(root); set_node_root(root, true); *internal_node_num_keys(root) = 1; *internal_node_child(root, 0) = left_child_page_num; - uint32_t left_child_max_key = get_node_max_key(left_child); + uint32_t left_child_max_key = get_node_max_key(table->pager, left_child); *internal_node_key(root, 0) = left_child_max_key; *internal_node_right_child(root) = right_child_page_num; *node_parent(left_child) = table->root_page_num; *node_parent(right_child) = table->root_page_num; } +void internal_node_split_and_insert(Table* table, uint32_t parent_page_num, + uint32_t child_page_num); + void internal_node_insert(Table* table, uint32_t parent_page_num, uint32_t child_page_num) { /* @@ -685,25 +722,39 @@ void internal_node_insert(Table* table, uint32_t parent_page_num, void* parent = get_page(table->pager, parent_page_num); void* child = get_page(table->pager, child_page_num); - uint32_t child_max_key = get_node_max_key(child); + uint32_t child_max_key = get_node_max_key(table->pager, child); uint32_t index = internal_node_find_child(parent, child_max_key); uint32_t original_num_keys = *internal_node_num_keys(parent); - *internal_node_num_keys(parent) = original_num_keys + 1; - if (original_num_keys >= INTERNAL_NODE_MAX_CELLS) { - printf("Need to implement splitting internal node\n"); - exit(EXIT_FAILURE); + if (original_num_keys >= INTERNAL_NODE_MAX_KEYS) { + internal_node_split_and_insert(table, parent_page_num, child_page_num); + return; } uint32_t right_child_page_num = *internal_node_right_child(parent); + /* + An internal node with a right child of INVALID_PAGE_NUM is empty + */ + if (right_child_page_num == INVALID_PAGE_NUM) { + *internal_node_right_child(parent) = child_page_num; + return; + } + void* right_child = get_page(table->pager, right_child_page_num); + /* + If we are already at the max number of cells for a node, we cannot increment + before splitting. Incrementing without inserting a new key/child pair + and immediately calling internal_node_split_and_insert has the effect + of creating a new key at (max_cells + 1) with an uninitialized value + */ + *internal_node_num_keys(parent) = original_num_keys + 1; - if (child_max_key > get_node_max_key(right_child)) { + if (child_max_key > get_node_max_key(table->pager, right_child)) { /* Replace right child */ *internal_node_child(parent, original_num_keys) = right_child_page_num; *internal_node_key(parent, original_num_keys) = - get_node_max_key(right_child); + get_node_max_key(table->pager, right_child); *internal_node_right_child(parent) = child_page_num; } else { /* Make room for the new cell */ @@ -722,6 +773,100 @@ void update_internal_node_key(void* node, uint32_t old_key, uint32_t new_key) { *internal_node_key(node, old_child_index) = new_key; } +void internal_node_split_and_insert(Table* table, uint32_t parent_page_num, + uint32_t child_page_num) { + uint32_t old_page_num = parent_page_num; + void* old_node = get_page(table->pager,parent_page_num); + uint32_t old_max = get_node_max_key(table->pager, old_node); + + void* child = get_page(table->pager, child_page_num); + uint32_t child_max = get_node_max_key(table->pager, child); + + uint32_t new_page_num = get_unused_page_num(table->pager); + + /* + Declaring a flag before updating pointers which + records whether this operation involves splitting the root - + if it does, we will insert our newly created node during + the step where the table's new root is created. If it does + not, we have to insert the newly created node into its parent + after the old node's keys have been transferred over. We are not + able to do this if the newly created node's parent is not a newly + initialized root node, because in that case its parent may have existing + keys aside from our old node which we are splitting. If that is true, we + need to find a place for our newly created node in its parent, and we + cannot insert it at the correct index if it does not yet have any keys + */ + uint32_t splitting_root = is_node_root(old_node); + + void* parent; + void* new_node; + if (splitting_root) { + create_new_root(table, new_page_num); + parent = get_page(table->pager,table->root_page_num); + /* + If we are splitting the root, we need to update old_node to point + to the new root's left child, new_page_num will already point to + the new root's right child + */ + old_page_num = *internal_node_child(parent,0); + old_node = get_page(table->pager, old_page_num); + } else { + parent = get_page(table->pager,*node_parent(old_node)); + new_node = get_page(table->pager, new_page_num); + initialize_internal_node(new_node); + } + + uint32_t* old_num_keys = internal_node_num_keys(old_node); + + uint32_t cur_page_num = *internal_node_right_child(old_node); + void* cur = get_page(table->pager, cur_page_num); + + /* + First put right child into new node and set right child of old node to invalid page number + */ + internal_node_insert(table, new_page_num, cur_page_num); + *node_parent(cur) = new_page_num; + *internal_node_right_child(old_node) = INVALID_PAGE_NUM; + /* + For each key until you get to the middle key, move the key and the child to the new node + */ + for (int i = INTERNAL_NODE_MAX_KEYS - 1; i > INTERNAL_NODE_MAX_KEYS / 2; i--) { + cur_page_num = *internal_node_child(old_node, i); + cur = get_page(table->pager, cur_page_num); + + internal_node_insert(table, new_page_num, cur_page_num); + *node_parent(cur) = new_page_num; + + (*old_num_keys)--; + } + + /* + Set child before middle key, which is now the highest key, to be node's right child, + and decrement number of keys + */ + *internal_node_right_child(old_node) = *internal_node_child(old_node,*old_num_keys - 1); + (*old_num_keys)--; + + /* + Determine which of the two nodes after the split should contain the child to be inserted, + and insert the child + */ + uint32_t max_after_split = get_node_max_key(table->pager, old_node); + + uint32_t destination_page_num = child_max < max_after_split ? old_page_num : new_page_num; + + internal_node_insert(table, destination_page_num, child_page_num); + *node_parent(child) = destination_page_num; + + update_internal_node_key(parent, old_max, get_node_max_key(table->pager, old_node)); + + if (!splitting_root) { + internal_node_insert(table,*node_parent(old_node),new_page_num); + *node_parent(new_node) = *node_parent(old_node); + } +} + void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { /* Create a new node and move half the cells over. @@ -730,7 +875,7 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { */ void* old_node = get_page(cursor->table->pager, cursor->page_num); - uint32_t old_max = get_node_max_key(old_node); + uint32_t old_max = get_node_max_key(cursor->table->pager, old_node); uint32_t new_page_num = get_unused_page_num(cursor->table->pager); void* new_node = get_page(cursor->table->pager, new_page_num); initialize_leaf_node(new_node); @@ -772,7 +917,7 @@ void leaf_node_split_and_insert(Cursor* cursor, uint32_t key, Row* value) { return create_new_root(cursor->table, new_page_num); } else { uint32_t parent_page_num = *node_parent(old_node); - uint32_t new_max = get_node_max_key(old_node); + uint32_t new_max = get_node_max_key(cursor->table->pager, old_node); void* parent = get_page(cursor->table->pager, parent_page_num); update_internal_node_key(parent, old_max, new_max); diff --git a/spec/main_spec.rb b/spec/main_spec.rb index d264105..f727c16 100644 --- a/spec/main_spec.rb +++ b/spec/main_spec.rb @@ -65,7 +65,7 @@ def run_script(commands) result = run_script(script) expect(result.last(2)).to match_array([ "db > Executed.", - "db > Need to implement splitting internal node", + "db > ", ]) end @@ -269,6 +269,163 @@ def run_script(commands) ]) end + it 'allows printing out the structure of a 7-leaf-node btree' do + script = [ + "insert 58 user58 person58@example.com", + "insert 56 user56 person56@example.com", + "insert 8 user8 person8@example.com", + "insert 54 user54 person54@example.com", + "insert 77 user77 person77@example.com", + "insert 7 user7 person7@example.com", + "insert 25 user25 person25@example.com", + "insert 71 user71 person71@example.com", + "insert 13 user13 person13@example.com", + "insert 22 user22 person22@example.com", + "insert 53 user53 person53@example.com", + "insert 51 user51 person51@example.com", + "insert 59 user59 person59@example.com", + "insert 32 user32 person32@example.com", + "insert 36 user36 person36@example.com", + "insert 79 user79 person79@example.com", + "insert 10 user10 person10@example.com", + "insert 33 user33 person33@example.com", + "insert 20 user20 person20@example.com", + "insert 4 user4 person4@example.com", + "insert 35 user35 person35@example.com", + "insert 76 user76 person76@example.com", + "insert 49 user49 person49@example.com", + "insert 24 user24 person24@example.com", + "insert 70 user70 person70@example.com", + "insert 48 user48 person48@example.com", + "insert 39 user39 person39@example.com", + "insert 15 user15 person15@example.com", + "insert 47 user47 person47@example.com", + "insert 30 user30 person30@example.com", + "insert 86 user86 person86@example.com", + "insert 31 user31 person31@example.com", + "insert 68 user68 person68@example.com", + "insert 37 user37 person37@example.com", + "insert 66 user66 person66@example.com", + "insert 63 user63 person63@example.com", + "insert 40 user40 person40@example.com", + "insert 78 user78 person78@example.com", + "insert 19 user19 person19@example.com", + "insert 46 user46 person46@example.com", + "insert 14 user14 person14@example.com", + "insert 81 user81 person81@example.com", + "insert 72 user72 person72@example.com", + "insert 6 user6 person6@example.com", + "insert 50 user50 person50@example.com", + "insert 85 user85 person85@example.com", + "insert 67 user67 person67@example.com", + "insert 2 user2 person2@example.com", + "insert 55 user55 person55@example.com", + "insert 69 user69 person69@example.com", + "insert 5 user5 person5@example.com", + "insert 65 user65 person65@example.com", + "insert 52 user52 person52@example.com", + "insert 1 user1 person1@example.com", + "insert 29 user29 person29@example.com", + "insert 9 user9 person9@example.com", + "insert 43 user43 person43@example.com", + "insert 75 user75 person75@example.com", + "insert 21 user21 person21@example.com", + "insert 82 user82 person82@example.com", + "insert 12 user12 person12@example.com", + "insert 18 user18 person18@example.com", + "insert 60 user60 person60@example.com", + "insert 44 user44 person44@example.com", + ".btree", + ".exit", + ] + result = run_script(script) + + expect(result[64...(result.length)]).to match_array([ + "db > Tree:", + "- internal (size 1)", + " - internal (size 2)", + " - leaf (size 7)", + " - 1", + " - 2", + " - 4", + " - 5", + " - 6", + " - 7", + " - 8", + " - key 8", + " - leaf (size 11)", + " - 9", + " - 10", + " - 12", + " - 13", + " - 14", + " - 15", + " - 18", + " - 19", + " - 20", + " - 21", + " - 22", + " - key 22", + " - leaf (size 8)", + " - 24", + " - 25", + " - 29", + " - 30", + " - 31", + " - 32", + " - 33", + " - 35", + " - key 35", + " - internal (size 3)", + " - leaf (size 12)", + " - 36", + " - 37", + " - 39", + " - 40", + " - 43", + " - 44", + " - 46", + " - 47", + " - 48", + " - 49", + " - 50", + " - 51", + " - key 51", + " - leaf (size 11)", + " - 52", + " - 53", + " - 54", + " - 55", + " - 56", + " - 58", + " - 59", + " - 60", + " - 63", + " - 65", + " - 66", + " - key 66", + " - leaf (size 7)", + " - 67", + " - 68", + " - 69", + " - 70", + " - 71", + " - 72", + " - 75", + " - key 75", + " - leaf (size 8)", + " - 76", + " - 77", + " - 78", + " - 79", + " - 81", + " - 82", + " - 85", + " - 86", + "db > ", + ]) + end + it 'prints constants' do script = [ ".constants", From 5163a27f6c5ef122ed1f83bebc94def15dd131f2 Mon Sep 17 00:00:00 2001 From: Luke Hawthorne Date: Tue, 23 May 2023 15:15:20 -0700 Subject: [PATCH 55/58] Add Part 14 writeup --- _parts/part14.md | 569 ++++++++++++++++++++++ assets/images/splitting-internal-node.png | Bin 0 -> 48730 bytes 2 files changed, 569 insertions(+) create mode 100644 _parts/part14.md create mode 100644 assets/images/splitting-internal-node.png diff --git a/_parts/part14.md b/_parts/part14.md new file mode 100644 index 0000000..e609bff --- /dev/null +++ b/_parts/part14.md @@ -0,0 +1,569 @@ +--- +title: Part 14 - Splitting Internal Nodes +date: 2023-05-23 +--- + +The next leg of our journey will be splitting internal nodes which are unable to accommodate new keys. Consider the example below: + +{% include image.html url="assets/images/splitting-internal-node.png" description="Example of splitting an internal" %} + +In this example, we add the key "11" to the tree. This will cause our root to split. When splitting an internal node, we will have to do a few things in order to keep everything straight: + +1. Create a sibling node to store (n-1)/2 of the original node's keys +2. Move these keys from the original node to the sibling node +3. Update the original node's key in the parent to reflect its new max key after splitting +4. Insert the sibling node into the parent (could result in the parent also being split) + +We will begin by replacing our stub code with the call to `internal_node_split_and_insert` + +```diff ++void internal_node_split_and_insert(Table* table, uint32_t parent_page_num, ++ uint32_t child_page_num); ++ + void internal_node_insert(Table* table, uint32_t parent_page_num, + uint32_t child_page_num) { + /* +@@ -685,25 +714,39 @@ void internal_node_insert(Table* table, uint32_t parent_page_num, + + void* parent = get_page(table->pager, parent_page_num); + void* child = get_page(table->pager, child_page_num); +- uint32_t child_max_key = get_node_max_key(child); ++ uint32_t child_max_key = get_node_max_key(table->pager, child); + uint32_t index = internal_node_find_child(parent, child_max_key); + + uint32_t original_num_keys = *internal_node_num_keys(parent); +- *internal_node_num_keys(parent) = original_num_keys + 1; + + if (original_num_keys >= INTERNAL_NODE_MAX_CELLS) { +- printf("Need to implement splitting internal node\n"); +- exit(EXIT_FAILURE); ++ internal_node_split_and_insert(table, parent_page_num, child_page_num); ++ return; + } + + uint32_t right_child_page_num = *internal_node_right_child(parent); ++ /* ++ An internal node with a right child of INVALID_PAGE_NUM is empty ++ */ ++ if (right_child_page_num == INVALID_PAGE_NUM) { ++ *internal_node_right_child(parent) = child_page_num; ++ return; ++ } ++ + void* right_child = get_page(table->pager, right_child_page_num); ++ /* ++ If we are already at the max number of cells for a node, we cannot increment ++ before splitting. Incrementing without inserting a new key/child pair ++ and immediately calling internal_node_split_and_insert has the effect ++ of creating a new key at (max_cells + 1) with an uninitialized value ++ */ ++ *internal_node_num_keys(parent) = original_num_keys + 1; + +- if (child_max_key > get_node_max_key(right_child)) { ++ if (child_max_key > get_node_max_key(table->pager, right_child)) { + /* Replace right child */ + *internal_node_child(parent, original_num_keys) = right_child_page_num; + *internal_node_key(parent, original_num_keys) = +- get_node_max_key(right_child); ++ get_node_max_key(table->pager, right_child); + *internal_node_right_child(parent) = child_page_num; +``` + +There are three important changes we are making here aside from replacing the stub: + - First, `internal_node_split_and_insert` is forward-declared because we will be calling `internal_node_insert` in its definition to avoid code duplication. + - In addition, we are moving the logic which increments the parent's number of keys further down in the function definition to ensure that this does not happen before the split. + - Finally, we are ensuring that a child node inserted into an empty internal node will become that internal node's right child without any other operations being performed, since an empty internal node has no keys to manipulate. + +The changes above require that we be able to identify an empty node - to this end, we will first define a constant which represents an invalid page number that is the child of every empty node. + +```diff ++#define INVALID_PAGE_NUM UINT32_MAX +``` +Now, when an internal node is initialized, we initialize its right child with this invalid page number. + +```diff +@@ -330,6 +335,12 @@ void initialize_internal_node(void* node) { + set_node_type(node, NODE_INTERNAL); + set_node_root(node, false); + *internal_node_num_keys(node) = 0; ++ /* ++ Necessary because the root page number is 0; by not initializing an internal ++ node's right child to an invalid page number when initializing the node, we may ++ end up with 0 as the node's right child, which makes the node a parent of the root ++ */ ++ *internal_node_right_child(node) = INVALID_PAGE_NUM; + } +``` + +This step was made necessary by a problem that the comment above attempts to summarize - when initializing an internal node without explicitly initializing the right child field, the value of that field at runtime could be 0 depending on the compiler or the architecture of the machine on which the program is being executed. Since we are using 0 as our root page number, this means that a newly allocated internal node will be a parent of the root. + +We have introduced some guards in our `internal_node_child` function to throw an error in the case of an attempt to access an invalid page. + +```diff +@@ -186,9 +188,19 @@ uint32_t* internal_node_child(void* node, uint32_t child_num) { + printf("Tried to access child_num %d > num_keys %d\n", child_num, num_keys); + exit(EXIT_FAILURE); + } else if (child_num == num_keys) { +- return internal_node_right_child(node); ++ uint32_t* right_child = internal_node_right_child(node); ++ if (*right_child == INVALID_PAGE_NUM) { ++ printf("Tried to access right child of node, but was invalid page\n"); ++ exit(EXIT_FAILURE); ++ } ++ return right_child; + } else { +- return internal_node_cell(node, child_num); ++ uint32_t* child = internal_node_cell(node, child_num); ++ if (*child == INVALID_PAGE_NUM) { ++ printf("Tried to access child %d of node, but was invalid page\n", child_num); ++ exit(EXIT_FAILURE); ++ } ++ return child; + } + } +``` + +One additional guard is needed in our `print_tree` function to ensure that we do not attempt to print an empty node, as that would involve trying to access an invalid page. + +```diff +@@ -294,15 +305,17 @@ void print_tree(Pager* pager, uint32_t page_num, uint32_t indentation_level) { + num_keys = *internal_node_num_keys(node); + indent(indentation_level); + printf("- internal (size %d)\n", num_keys); +- for (uint32_t i = 0; i < num_keys; i++) { +- child = *internal_node_child(node, i); ++ if (num_keys > 0) { ++ for (uint32_t i = 0; i < num_keys; i++) { ++ child = *internal_node_child(node, i); ++ print_tree(pager, child, indentation_level + 1); ++ ++ indent(indentation_level + 1); ++ printf("- key %d\n", *internal_node_key(node, i)); ++ } ++ child = *internal_node_right_child(node); + print_tree(pager, child, indentation_level + 1); +- +- indent(indentation_level + 1); +- printf("- key %d\n", *internal_node_key(node, i)); + } +- child = *internal_node_right_child(node); +- print_tree(pager, child, indentation_level + 1); + break; + } + } +``` + +Now for the headliner, `internal_node_split_and_insert`. We will first provide it in its entirety, and then break it down by steps. + +```diff ++void internal_node_split_and_insert(Table* table, uint32_t parent_page_num, ++ uint32_t child_page_num) { ++ uint32_t old_page_num = parent_page_num; ++ void* old_node = get_page(table->pager,parent_page_num); ++ uint32_t old_max = get_node_max_key(table->pager, old_node); ++ ++ void* child = get_page(table->pager, child_page_num); ++ uint32_t child_max = get_node_max_key(table->pager, child); ++ ++ uint32_t new_page_num = get_unused_page_num(table->pager); ++ ++ /* ++ Declaring a flag before updating pointers which ++ records whether this operation involves splitting the root - ++ if it does, we will insert our newly created node during ++ the step where the table's new root is created. If it does ++ not, we have to insert the newly created node into its parent ++ after the old node's keys have been transferred over. We are not ++ able to do this if the newly created node's parent is not a newly ++ initialized root node, because in that case its parent may have existing ++ keys aside from our old node which we are splitting. If that is true, we ++ need to find a place for our newly created node in its parent, and we ++ cannot insert it at the correct index if it does not yet have any keys ++ */ ++ uint32_t splitting_root = is_node_root(old_node); ++ ++ void* parent; ++ void* new_node; ++ if (splitting_root) { ++ create_new_root(table, new_page_num); ++ parent = get_page(table->pager,table->root_page_num); ++ /* ++ If we are splitting the root, we need to update old_node to point ++ to the new root's left child, new_page_num will already point to ++ the new root's right child ++ */ ++ old_page_num = *internal_node_child(parent,0); ++ old_node = get_page(table->pager, old_page_num); ++ } else { ++ parent = get_page(table->pager,*node_parent(old_node)); ++ new_node = get_page(table->pager, new_page_num); ++ initialize_internal_node(new_node); ++ } ++ ++ uint32_t* old_num_keys = internal_node_num_keys(old_node); ++ ++ uint32_t cur_page_num = *internal_node_right_child(old_node); ++ void* cur = get_page(table->pager, cur_page_num); ++ ++ /* ++ First put right child into new node and set right child of old node to invalid page number ++ */ ++ internal_node_insert(table, new_page_num, cur_page_num); ++ *node_parent(cur) = new_page_num; ++ *internal_node_right_child(old_node) = INVALID_PAGE_NUM; ++ /* ++ For each key until you get to the middle key, move the key and the child to the new node ++ */ ++ for (int i = INTERNAL_NODE_MAX_CELLS - 1; i > INTERNAL_NODE_MAX_CELLS / 2; i--) { ++ cur_page_num = *internal_node_child(old_node, i); ++ cur = get_page(table->pager, cur_page_num); ++ ++ internal_node_insert(table, new_page_num, cur_page_num); ++ *node_parent(cur) = new_page_num; ++ ++ (*old_num_keys)--; ++ } ++ ++ /* ++ Set child before middle key, which is now the highest key, to be node's right child, ++ and decrement number of keys ++ */ ++ *internal_node_right_child(old_node) = *internal_node_child(old_node,*old_num_keys - 1); ++ (*old_num_keys)--; ++ ++ /* ++ Determine which of the two nodes after the split should contain the child to be inserted, ++ and insert the child ++ */ ++ uint32_t max_after_split = get_node_max_key(table->pager, old_node); ++ ++ uint32_t destination_page_num = child_max < max_after_split ? old_page_num : new_page_num; ++ ++ internal_node_insert(table, destination_page_num, child_page_num); ++ *node_parent(child) = destination_page_num; ++ ++ update_internal_node_key(parent, old_max, get_node_max_key(table->pager, old_node)); ++ ++ if (!splitting_root) { ++ internal_node_insert(table,*node_parent(old_node),new_page_num); ++ *node_parent(new_node) = *node_parent(old_node); ++ } ++} ++ +``` + +The first thing we need to do is create a variable to store the page number of the node we are splitting (the old node from here out). This is necessary because the page number of the old node will change if it happens to be the table's root node. We also need to remember what the node's current max is, because that value represents its key in the parent, and that key will need to be updated with the old node's new maximum after the split occurs. + +```diff ++ uint32_t old_page_num = parent_page_num; ++ void* old_node = get_page(table->pager,parent_page_num); ++ uint32_t old_max = get_node_max_key(table->pager, old_node); +``` + +The next important step is the branching logic which depends on whether the old node is the table's root node. We will need to keep track of this value for later use; as the comment attempts to convey, we run into a problem if we do not store this information at the beginning of our function definition - if we are not splitting the root, we cannot insert our newly created sibling node into the old node's parent right away, because it does not yet contain any keys and therefore will not be placed at the right index among the other key/child pairs which may or may not already be present in the parent node. + +```diff ++ uint32_t splitting_root = is_node_root(old_node); ++ ++ void* parent; ++ void* new_node; ++ if (splitting_root) { ++ create_new_root(table, new_page_num); ++ parent = get_page(table->pager,table->root_page_num); ++ /* ++ If we are splitting the root, we need to update old_node to point ++ to the new root's left child, new_page_num will already point to ++ the new root's right child ++ */ ++ old_page_num = *internal_node_child(parent,0); ++ old_node = get_page(table->pager, old_page_num); ++ } else { ++ parent = get_page(table->pager,*node_parent(old_node)); ++ new_node = get_page(table->pager, new_page_num); ++ initialize_internal_node(new_node); ++ } +``` + +Once we have settled the question of splitting or not splitting the root, we begin moving keys from the old node to its sibling. We must first move the old node's right child and set its right child field to an invalid page to indicate that it is empty. Now, we loop over the old node's remaining keys, performing the following steps on each iteration: + 1. Obtain a reference to the old node's key and child at the current index + 2. Insert the child into the sibling node + 3. Update the child's parent value to point to the sibling node + 4. Decrement the old node's number of keys + +```diff ++ uint32_t* old_num_keys = internal_node_num_keys(old_node); ++ ++ uint32_t cur_page_num = *internal_node_right_child(old_node); ++ void* cur = get_page(table->pager, cur_page_num); ++ ++ /* ++ First put right child into new node and set right child of old node to invalid page number ++ */ ++ internal_node_insert(table, new_page_num, cur_page_num); ++ *node_parent(cur) = new_page_num; ++ *internal_node_right_child(old_node) = INVALID_PAGE_NUM; ++ /* ++ For each key until you get to the middle key, move the key and the child to the new node ++ */ ++ for (int i = INTERNAL_NODE_MAX_CELLS - 1; i > INTERNAL_NODE_MAX_CELLS / 2; i--) { ++ cur_page_num = *internal_node_child(old_node, i); ++ cur = get_page(table->pager, cur_page_num); ++ ++ internal_node_insert(table, new_page_num, cur_page_num); ++ *node_parent(cur) = new_page_num; ++ ++ (*old_num_keys)--; ++ } +``` + +Step 4 is important, because it serves the purpose of "erasing" the key/child pair from the old node. Although we are not actually freeing the memory at that byte offset in the old node's page, by decrementing the old node's number of keys we are making that memory location inaccessible, and the bytes will be overwritten the next time a child is inserted into the old node. + +Also note the behavior of our loop invariant - if our maximum number of internal node keys changes in the future, our logic ensures that both our old node and our sibling node will end up with (n-1)/2 keys after the split, with the 1 remaining node going to the parent. If an even number is chosen as the maximum number of nodes, n/2 nodes will remain with the old node while (n-1)/2 will be moved to the sibling node. This logic would be straightforward to revise as needed. + +Once the keys to be moved have been, we set the old node's i'th child as its right child and decrement its number of keys. + +```diff ++ /* ++ Set child before middle key, which is now the highest key, to be node's right child, ++ and decrement number of keys ++ */ ++ *internal_node_right_child(old_node) = *internal_node_child(old_node,*old_num_keys - 1); ++ (*old_num_keys)--; +``` + +We then insert the child node into either the old node or the sibling node depending on the value of its max key. + +```diff ++ uint32_t max_after_split = get_node_max_key(table->pager, old_node); ++ ++ uint32_t destination_page_num = child_max < max_after_split ? old_page_num : new_page_num; ++ ++ internal_node_insert(table, destination_page_num, child_page_num); ++ *node_parent(child) = destination_page_num; +``` + +Finally, we update the old node's key in its parent, and insert the sibling node and update the sibling node's parent pointer if necessary. + +```diff ++ update_internal_node_key(parent, old_max, get_node_max_key(table->pager, old_node)); ++ ++ if (!splitting_root) { ++ internal_node_insert(table,*node_parent(old_node),new_page_num); ++ *node_parent(new_node) = *node_parent(old_node); ++ } +``` + +One important change required to support this new logic is in our `create_new_root` function. Before, we were only taking into account situations where the new root's children would be leaf nodes. If the new root's children are instead internal nodes, we need to do two things: + 1. Correctly initialize the root's new children to be internal nodes + 2. In addition to the call to memcpy, we need to insert each of the root's keys into its new left child and update the parent pointer of each of those children + +```diff +@@ -661,22 +680,40 @@ void create_new_root(Table* table, uint32_t right_child_page_num) { + uint32_t left_child_page_num = get_unused_page_num(table->pager); + void* left_child = get_page(table->pager, left_child_page_num); + ++ if (get_node_type(root) == NODE_INTERNAL) { ++ initialize_internal_node(right_child); ++ initialize_internal_node(left_child); ++ } ++ + /* Left child has data copied from old root */ + memcpy(left_child, root, PAGE_SIZE); + set_node_root(left_child, false); + ++ if (get_node_type(left_child) == NODE_INTERNAL) { ++ void* child; ++ for (int i = 0; i < *internal_node_num_keys(left_child); i++) { ++ child = get_page(table->pager, *internal_node_child(left_child,i)); ++ *node_parent(child) = left_child_page_num; ++ } ++ child = get_page(table->pager, *internal_node_right_child(left_child)); ++ *node_parent(child) = left_child_page_num; ++ } ++ + /* Root node is a new internal node with one key and two children */ + initialize_internal_node(root); + set_node_root(root, true); + *internal_node_num_keys(root) = 1; + *internal_node_child(root, 0) = left_child_page_num; +- uint32_t left_child_max_key = get_node_max_key(left_child); ++ uint32_t left_child_max_key = get_node_max_key(table->pager, left_child); + *internal_node_key(root, 0) = left_child_max_key; + *internal_node_right_child(root) = right_child_page_num; + *node_parent(left_child) = table->root_page_num; + *node_parent(right_child) = table->root_page_num; + } +``` + +Another important change has been made to `get_node_max_key`, as mentioned at the beginning of this article. Since an internal node's key represents the maximum of the tree pointed to by the child to its left, and that child can be a tree of arbitrary depth, we need to walk down the right children of that tree until we get to a leaf node, and then take the maximum key of that leaf node. + +```diff ++uint32_t get_node_max_key(Pager* pager, void* node) { ++ if (get_node_type(node) == NODE_LEAF) { ++ return *leaf_node_key(node, *leaf_node_num_cells(node) - 1); ++ } ++ void* right_child = get_page(pager,*internal_node_right_child(node)); ++ return get_node_max_key(pager, right_child); ++} +``` + +We have written a single test to demonstrate that our `print_tree` function still works after the introduction of internal node splitting. + +```diff ++ it 'allows printing out the structure of a 7-leaf-node btree' do ++ script = [ ++ "insert 58 user58 person58@example.com", ++ "insert 56 user56 person56@example.com", ++ "insert 8 user8 person8@example.com", ++ "insert 54 user54 person54@example.com", ++ "insert 77 user77 person77@example.com", ++ "insert 7 user7 person7@example.com", ++ "insert 25 user25 person25@example.com", ++ "insert 71 user71 person71@example.com", ++ "insert 13 user13 person13@example.com", ++ "insert 22 user22 person22@example.com", ++ "insert 53 user53 person53@example.com", ++ "insert 51 user51 person51@example.com", ++ "insert 59 user59 person59@example.com", ++ "insert 32 user32 person32@example.com", ++ "insert 36 user36 person36@example.com", ++ "insert 79 user79 person79@example.com", ++ "insert 10 user10 person10@example.com", ++ "insert 33 user33 person33@example.com", ++ "insert 20 user20 person20@example.com", ++ "insert 4 user4 person4@example.com", ++ "insert 35 user35 person35@example.com", ++ "insert 76 user76 person76@example.com", ++ "insert 49 user49 person49@example.com", ++ "insert 24 user24 person24@example.com", ++ "insert 70 user70 person70@example.com", ++ "insert 48 user48 person48@example.com", ++ "insert 39 user39 person39@example.com", ++ "insert 15 user15 person15@example.com", ++ "insert 47 user47 person47@example.com", ++ "insert 30 user30 person30@example.com", ++ "insert 86 user86 person86@example.com", ++ "insert 31 user31 person31@example.com", ++ "insert 68 user68 person68@example.com", ++ "insert 37 user37 person37@example.com", ++ "insert 66 user66 person66@example.com", ++ "insert 63 user63 person63@example.com", ++ "insert 40 user40 person40@example.com", ++ "insert 78 user78 person78@example.com", ++ "insert 19 user19 person19@example.com", ++ "insert 46 user46 person46@example.com", ++ "insert 14 user14 person14@example.com", ++ "insert 81 user81 person81@example.com", ++ "insert 72 user72 person72@example.com", ++ "insert 6 user6 person6@example.com", ++ "insert 50 user50 person50@example.com", ++ "insert 85 user85 person85@example.com", ++ "insert 67 user67 person67@example.com", ++ "insert 2 user2 person2@example.com", ++ "insert 55 user55 person55@example.com", ++ "insert 69 user69 person69@example.com", ++ "insert 5 user5 person5@example.com", ++ "insert 65 user65 person65@example.com", ++ "insert 52 user52 person52@example.com", ++ "insert 1 user1 person1@example.com", ++ "insert 29 user29 person29@example.com", ++ "insert 9 user9 person9@example.com", ++ "insert 43 user43 person43@example.com", ++ "insert 75 user75 person75@example.com", ++ "insert 21 user21 person21@example.com", ++ "insert 82 user82 person82@example.com", ++ "insert 12 user12 person12@example.com", ++ "insert 18 user18 person18@example.com", ++ "insert 60 user60 person60@example.com", ++ "insert 44 user44 person44@example.com", ++ ".btree", ++ ".exit", ++ ] ++ result = run_script(script) ++ ++ expect(result[64...(result.length)]).to match_array([ ++ "db > Tree:", ++ "- internal (size 1)", ++ " - internal (size 2)", ++ " - leaf (size 7)", ++ " - 1", ++ " - 2", ++ " - 4", ++ " - 5", ++ " - 6", ++ " - 7", ++ " - 8", ++ " - key 8", ++ " - leaf (size 11)", ++ " - 9", ++ " - 10", ++ " - 12", ++ " - 13", ++ " - 14", ++ " - 15", ++ " - 18", ++ " - 19", ++ " - 20", ++ " - 21", ++ " - 22", ++ " - key 22", ++ " - leaf (size 8)", ++ " - 24", ++ " - 25", ++ " - 29", ++ " - 30", ++ " - 31", ++ " - 32", ++ " - 33", ++ " - 35", ++ " - key 35", ++ " - internal (size 3)", ++ " - leaf (size 12)", ++ " - 36", ++ " - 37", ++ " - 39", ++ " - 40", ++ " - 43", ++ " - 44", ++ " - 46", ++ " - 47", ++ " - 48", ++ " - 49", ++ " - 50", ++ " - 51", ++ " - key 51", ++ " - leaf (size 11)", ++ " - 52", ++ " - 53", ++ " - 54", ++ " - 55", ++ " - 56", ++ " - 58", ++ " - 59", ++ " - 60", ++ " - 63", ++ " - 65", ++ " - 66", ++ " - key 66", ++ " - leaf (size 7)", ++ " - 67", ++ " - 68", ++ " - 69", ++ " - 70", ++ " - 71", ++ " - 72", ++ " - 75", ++ " - key 75", ++ " - leaf (size 8)", ++ " - 76", ++ " - 77", ++ " - 78", ++ " - 79", ++ " - 81", ++ " - 82", ++ " - 85", ++ " - 86", ++ "db > ", ++ ]) ++ end +``` diff --git a/assets/images/splitting-internal-node.png b/assets/images/splitting-internal-node.png new file mode 100644 index 0000000000000000000000000000000000000000..8d116aeb9484972c14579963cf5a96bb8154b5c9 GIT binary patch literal 48730 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 h_qxC+MGLw1|O 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 literal 0 HcmV?d00001 From e95d92efba3bf37e477ff2bf55f7c08fe6d9ba28 Mon Sep 17 00:00:00 2001 From: Luke Hawthorne Date: Tue, 23 May 2023 15:20:23 -0700 Subject: [PATCH 56/58] Add dependency on webrick gem which is no longer bundled with ruby; update lock file and ensure cross-platform support --- Gemfile | 1 + Gemfile.lock | 379 +++++++++++++++++++++++++++------------------------ 2 files changed, 202 insertions(+), 178 deletions(-) diff --git a/Gemfile b/Gemfile index 3d80de9..4648a51 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source 'https://rubygems.org' gem 'github-pages', group: :jekyll_plugins +gem 'webrick' gem "jekyll-theme-minimal" gem "rspec" diff --git a/Gemfile.lock b/Gemfile.lock index a279432..92c7b76 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,264 +1,287 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + activesupport (7.0.4.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.0.5) - diff-lcs (1.3) - dnsruby (1.61.2) - addressable (~> 2.5) - em-websocket (0.5.1) + commonmarker (0.23.9) + concurrent-ruby (1.2.2) + diff-lcs (1.5.0) + dnsruby (1.70.0) + simpleidn (~> 0.2.1) + em-websocket (0.5.3) eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.11.0) - ffi (>= 1.3.0) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.7.0) - faraday (0.15.3) - multipart-post (>= 1.2, < 3) - ffi (1.9.25) + eventmachine (1.2.7-x86-mingw32) + execjs (2.8.1) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5) + ffi (1.15.5-x86-mingw32) forwardable-extended (2.6.0) - gemoji (3.0.0) - github-pages (192) - activesupport (= 4.2.10) - github-pages-health-check (= 1.8.1) - jekyll (= 3.7.4) - jekyll-avatar (= 0.6.0) + gemoji (3.0.1) + github-pages (228) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.3) + jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.5) + jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.10.0) + jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.9.4) - jekyll-mentions (= 1.4.1) - jekyll-optional-front-matter (= 0.3.0) + jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.2.0) - jekyll-redirect-from (= 0.14.0) - jekyll-relative-links (= 0.5.3) - jekyll-remote-theme (= 0.3.1) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.5.0) - jekyll-sitemap (= 1.2.0) - jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.1.1) - jekyll-theme-cayman (= 0.1.1) - jekyll-theme-dinky (= 0.1.1) - jekyll-theme-hacker (= 0.1.1) - jekyll-theme-leap-day (= 0.1.1) - jekyll-theme-merlot (= 0.1.1) - jekyll-theme-midnight (= 0.1.1) - jekyll-theme-minimal (= 0.1.1) - jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.3) - jekyll-theme-slate (= 0.1.1) - jekyll-theme-tactile (= 0.1.1) - jekyll-theme-time-machine (= 0.1.1) - jekyll-titles-from-headings (= 0.5.1) - jemoji (= 0.10.1) - kramdown (= 1.17.0) - liquid (= 4.0.0) - listen (= 3.1.5) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) mercenary (~> 0.3) - minima (= 2.5.0) - nokogiri (>= 1.8.2, < 2.0) - rouge (= 2.2.1) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.26.0) terminal-table (~> 1.4) - github-pages-health-check (1.8.1) + github-pages-health-check (1.17.9) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (~> 2.0) + public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.8.4) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) - http_parser.rb (0.6.0) - i18n (0.9.5) + http_parser.rb (0.8.0) + i18n (1.13.0) concurrent-ruby (~> 1.0) - jekyll (3.7.4) + jekyll (3.9.3) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) - kramdown (~> 1.14) + kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.6.0) - jekyll (~> 3.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.2.0) - commonmarker (~> 0.14) - jekyll (>= 3.0, < 4.0) - jekyll-commonmark-ghpages (0.1.5) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1) - rouge (~> 2) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.10.0) - jekyll (~> 3.3) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.4) - jekyll (~> 3.1) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.4.1) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.3.0) - jekyll (~> 3.0) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) - jekyll-readme-index (0.2.0) - jekyll (~> 3.0) - jekyll-redirect-from (0.14.0) - jekyll (~> 3.3) - jekyll-relative-links (0.5.3) - jekyll (~> 3.3) - jekyll-remote-theme (0.3.1) - jekyll (~> 3.5) - rubyzip (>= 1.2.1, < 3.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.5.0) - jekyll (~> 3.3) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-swiss (0.4.0) - jekyll-theme-architect (0.1.1) - jekyll (~> 3.5) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.1) - jekyll (~> 3.5) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.1) - jekyll (~> 3.5) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.1) - jekyll (~> 3.5) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.1) - jekyll (~> 3.5) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.1) - jekyll (~> 3.5) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.1) - jekyll (~> 3.5) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.1) - jekyll (~> 3.5) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.1) - jekyll (~> 3.5) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.3) - jekyll (~> 3.5) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.1.1) - jekyll (~> 3.5) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.1) - jekyll (~> 3.5) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.1) - jekyll (~> 3.5) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.1) - jekyll (~> 3.3) - jekyll-watch (2.0.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.10.1) + jemoji (0.12.0) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (~> 3.0) - kramdown (1.17.0) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.4.0) - minima (2.5.0) - jekyll (~> 3.5) + mini_portile2 (2.8.2) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.11.3) - multipart-post (2.0.0) - nokogiri (1.10.8) - mini_portile2 (~> 2.4.0) - octokit (4.12.0) - sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) + minitest (5.18.0) + nokogiri (1.15.1) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.15.1-x86-mingw32) + racc (~> 1.4) + nokogiri (1.15.1-x86_64-linux) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (2.0.5) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (2.2.1) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.1) + public_suffix (4.0.7) + racc (1.6.2) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.26.0) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - ruby-enum (0.7.2) - i18n - ruby_dep (1.5.0) - rubyzip (1.2.2) - safe_yaml (1.0.4) - sass (3.6.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.3.0) + typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - unicode-display_width (1.4.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unf_ext (0.0.8.2-x86-mingw32) + unicode-display_width (1.8.0) + webrick (1.8.1) PLATFORMS - ruby + x86-mingw32 + x86-mswin32-60 + x86_64-linux DEPENDENCIES github-pages jekyll-theme-minimal rspec + webrick BUNDLED WITH - 2.0.1 + 2.2.33 From 91d2f4c96063d1f4d68e4fd335af60a773c935e4 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Mon, 4 Mar 2024 09:14:47 -0500 Subject: [PATCH 57/58] Banner saying project is no longer under development --- Gemfile.lock | 3 +++ _layouts/default.html | 7 +++++++ _parts/part15.md | 16 ++++++++++++++++ assets/images/code-crafters.jpeg | Bin 0 -> 36254 bytes 4 files changed, 26 insertions(+) create mode 100644 _parts/part15.md create mode 100644 assets/images/code-crafters.jpeg diff --git a/Gemfile.lock b/Gemfile.lock index 92c7b76..a639b67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -217,6 +217,8 @@ GEM nokogiri (1.15.1) mini_portile2 (~> 2.8.2) racc (~> 1.4) + nokogiri (1.15.1-arm64-darwin) + racc (~> 1.4) nokogiri (1.15.1-x86-mingw32) racc (~> 1.4) nokogiri (1.15.1-x86_64-linux) @@ -273,6 +275,7 @@ GEM webrick (1.8.1) PLATFORMS + arm64-darwin-21 x86-mingw32 x86-mswin32-60 x86_64-linux diff --git a/_layouts/default.html b/_layouts/default.html index 210b8d3..8bf4a2d 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -35,6 +35,13 @@

{{ site.title | default: site.github.repository_name }}

  • View On GitHub
  • {% endif %} + +
    +

    This project is no longer under active development. You can read more here. But if you'd like to keep learning how to make your own SQLite clone from scratch, or one of many other projects like Docker, Redis, Git or BitTorrent, try CodeCrafters.

    +
    +
    + {{ include.description }} +
    diff --git a/_parts/part15.md b/_parts/part15.md new file mode 100644 index 0000000..6e90ce9 --- /dev/null +++ b/_parts/part15.md @@ -0,0 +1,16 @@ +--- +title: Part 15 - Where to go next +date: 2024-03-04 +--- + +This project is no longer under active development. + +But if you'd like to keep learning how to make your own SQLite clone from scratch, or one of many other projects like Docker, Redis, Git or BitTorrent, try CodeCrafters. + +CodeCrafters maintains a pretty comprehensive list of "Build your own X" tutorials including "Build your own Database". + +Plus, if your company has a learning and development budget, you can use it to pay for CodeCrafter's paid service: + +{{ include.description }} + +If you use my referral link, I get a commision. \ No newline at end of file diff --git a/assets/images/code-crafters.jpeg b/assets/images/code-crafters.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4fd364cb0d12e9441fbf5fa6aefddbe07b8efe88 GIT binary patch literal 36254 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 literal 0 HcmV?d00001 From 60b50c5b7be787a4aaa1e50ab8a90c6cabb75159 Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Mon, 4 Mar 2024 09:18:03 -0500 Subject: [PATCH 58/58] Fix url --- _layouts/default.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_layouts/default.html b/_layouts/default.html index 8bf4a2d..8165390 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -37,7 +37,7 @@

    {{ site.title | default: site.github.repository_name }}

    {% endif %}
    -

    This project is no longer under active development. You can read more here. But if you'd like to keep learning how to make your own SQLite clone from scratch, or one of many other projects like Docker, Redis, Git or BitTorrent, try CodeCrafters.

    +

    This project is no longer under active development. You can read more here. But if you'd like to keep learning how to make your own SQLite clone from scratch, or one of many other projects like Docker, Redis, Git or BitTorrent, try CodeCrafters.

    {{ include.description }}