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 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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 }}