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/56] 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 74f35581b29335144c32c21750b195fa01c3ebba Mon Sep 17 00:00:00 2001 From: Connor Stack Date: Sun, 22 Oct 2017 18:33:18 -0700 Subject: [PATCH 02/56] 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 03/56] 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 04/56] 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 05/56] 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 06/56] 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 07/56] 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 08/56] 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 09/56] 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 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

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 10/56] 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 11/56] 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 12/56] 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 13/56] 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 14/56] 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 15/56] 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 16/56] 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 17/56] 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 18/56] 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 19/56] 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 20/56] 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 21/56] 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 22/56] 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 23/56] 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 24/56] 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 25/56] 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 26/56] 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 27/56] 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 28/56] 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 29/56] 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 30/56] 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 31/56] 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 32/56] 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 33/56] 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 34/56] 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 35/56] 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 36/56] 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 37/56] 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 38/56] 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 39/56] 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 40/56] 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 41/56] 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 42/56] 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 43/56] 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 44/56] 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 45/56] 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 46/56] 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 47/56] 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 48/56] 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 49/56] 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 50/56] 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 51/56] 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 52/56] 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 53/56] 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 z!N>aZi{7I{@gQ69|M&6zYL1SFn~!Pu>~BQSiAwTX@mqI(vrYXsafD!7)aty7{CfmV zYlmFrA@m;-R&ar%Nr!vV#HwAaf0KaQ<}t1wU1b8BGBsCNP$^Yog+Y^S%jrQOJofIl zZvL15{J%f4)?!<1+HanikU>a}5?>dIya>2CiY%<^KbTppWz|q zRW6{2$##JMDwQFM2%gpho^+LHG8A&Rv)xZ9J6Or*8m)6D(p57H2?K4G)W>LCE!T2> z|IgzXA%oZiY*|-X7o(Nu^@3Mvn)b*;|21MNvDfxh%(FEgv`Quht5yc!8t>db27e=^ zO4InQFxJs6lXTzM%zQH#v$GT@v5W;+!UregjTgC%dsk@krL=dbloMDVy!%)#&$OHEg`)ehx$K> zGSap6Nh0PnGQq_D#X2R-|057E+s{Tfu5=@Ciwp|WKwCNYrX@qtEyo3nRROMH80~4c ziPm(BDSi+ktu`ge6w(B7NLTH5DPcX*m|qH9fZSNp4CF7b2cUftq^>3(Q$keGsin%w zTpuH$jv%5eM5&yP-R2=mnBVS1DMi3M$Cl<}XXl8;K#@v|Z1w8V9|e3zZsedkx4nRBmQk<0B2m+%ylB8peDLcJzx$pa=Ap#sK}*YTCa6`qgW+RXlw~U(+R<6W zyU1am=8#9WXl_pRIvCwD=8xAn%jfNxGToZAPiK3IQ(au7RDiUsuwmb~(Et4O8{`@Z zx+&QouTq)$-YMs5vo)ub2Ug9@UnoNG`x|V3EAU&LQLphvB!%KhnxR|E{PD6Orha5h zy4;oiDMgU_4Z^?cRUXdPyCV^l#vuPd-?N~ zlqilkOoyv(XL8u3@P|fdvO)m1P_dcFrnhJz6ki(M^zYA?GpV4{0d?72Wk^>PU4FN? z7V?ea3EWk}- zqTztQN0(YIE8peWNM#!a2gqv~Bo>F%8*zokb1OQ(P%Z>aKc05v5B?rs;=#<%qlPck zQbyr$&l=7Y@`B|0dhcaCCGHWxc$RYs33ArQe!I$`&rT9j~+^Vh=^+HiCQ*Pq`sN&?0U zi}1EsjBmP_77UwSeT^AX-<#i~J=n0n0+(tMXrzu*`p-Wtko;&y+J( zn(e?sw5{Cpz{&+Iop^ncF8`T%P=qY(9?7PfDiKE2JHn}$_*|n{m(bOAx*kGmGlIur(b6csauvVR8&yFt3|IKrfml|NKeshe*SEhE9l0b zDv5vI!n`;?GF%6(^OpxQqf+>-jc{&Bbp++IVPF&H6EQqduVRZ}_dKwCt4SR6r=Puk zJ&R*IQ=RO#H6sNuky+fye7CiIq&l%CtdBBU_&ru}iuD?p$BGQbDnoM^|I%^C{f4)t zS9=~uoSvs^lMc~&odnj3Rek+S>ie$KE{$&I*vq}q5eo6#3){Z3=YNSS{^$nil$PD8 zm^gHPN>pT%7#VkgaB$_~st)&IgG?3yb%#o)s;mZnoNLy(5uRj2bu-1qv;Q(MBbgf$ z`SGDaVF`l({sy;-eJhq(eFU$6DY&y%Wx;RJ@2&UIw!vk-35SA9JqeUZSj$|tzxYdo z!FY6Et4?Pkgxh8wXbi9ZAtwkfx3KNXqhbHE05pb+n5}f=D}&W#9J_e+Ch>CEBq$9t z#r2X4yKy(TN{amTupO=&0N$A!TR1QXV0>$F2M$xi`9%P77xzF0Ph)Jv1=-$^vU>pc zU|HOW&UM+G#8t?=&_4k*A)qbZYtp+4((T-EEsB6IFL&{-iuzlEwY8?S$E!`-PN5<0RoEk; zVe_B26kdX3m&&93b2Bs|+3(4v;>tZ}-b4)94}~YY$gjD8eI+TLZ}SFvT~#ETH$MXO zEP9Q%?$e{ar&o?qtD9U>dMEIAqM@(+?qVm|Wjfdl6;k+dSrrpBh0@X^+ust}$nHx` zn%#CZ#!b0T`j^+)DuJrjuKuTEf0?_uB0$>c4NDvRJ+-FbT>&?-;=lJ1J#^c}7`DAD z3IBKOX#rfd^27c!*Z+M7{l{PofWRd1M^pW^t4xB1ctH{AQN@3)hp!U=oTxf4r1|fm n?gH=HZhC9d{iR3sKRbR6o&ySlC+E;>;749YMY>$l=*|BDx)`fF 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 54/56] 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 55/56] 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 zcmeFZ2RNKv*DyMWPJ~2ni4ct5O9T-^7$q1)j~2ZXy(L5^Q4>ZTWAxs834$PcFQX69 zd;cfT`@Y}zmhXMefBy5I|2o%ozBOF;zW2S$D*N7h?X}jvZzpdT0rwQ;6yyLH7ytkU z`Ukk30R#c?aBy&ONbqoRamnxD-KV6wPj&Ae88tcOJt|6aYD%hm=pQu`EfvipbUqyi z{X=FJHa0dYS}s1WC%jCoY)>%o@$rd?h^g-0rQ&CzXJTaq0)bEXxwu$?tWTaiVP!@C zu?ldZlTZG7|Chw?jK9EbD}WRa6L5zE3xgDZNs56*igEi5K!-*U6AR-vu73kuygS%9 z=sfh(*Y^OJ7?_xM@Nw`6@8Dx$VWTO+#KOKqibM8@5BCAZb2W8Mr_cA9j00nb_=TKZ zGRw*<$SEHS2+EkazRw!gii>ZlA&( zz`BEtiGzM7K#Il*0}Bfa69*FwSbsdjB)zYGhfM9kBR+xWO#H@9fqRe3WHLVwA@`XD z+is@;L|Eubq*$Z?DL|Ud1Vme@9?&&fq-UqFQZIFe(MhR~`yl4^fB*mgmc@7i;I zO2#|!tM-g94CB0}x_4E%uC}8W@cr*b;s2o|Ni-8pPqQoTbd*OgpR>1gcf0*3>i&!Mf_dTc$WKQMllK;EtyL7 zY@q06hvfF#3aoX97G3WCt~pn@F4zmMC15iW>v@n2;4!g)*tWm*1(57fR&5ICcZ!%3})sX|1#~&SzF1u#6+2u)aiyz7* zGKpCbB$!fjj@yXVq^*6Sx~d*B+Ge1=Jo9dyUgQ?#$fjV$hzPc(t}0PcRO@%p?R!K0 z@bA$6zmt?0o#S4^5qkEW#6x))>t9+C(r$Xx`ubtM$)=y$6^+vjyH{Nj{HryBgLSV= z&t9mzp1%<_HjRR^jUG_>6^T@xjxHc$#S>h=7$5F(yQOM*`gn_dRp_ZxSi8_EpYVGC zIL04{(?AI4)CeBfj4MRFOr^c)o01bd|4{#v06(eCTfp0ivyfYW<=V_xP@0Kk&#Eg(pdhV&as9(Q$LQ@~HvNJES8ZG%^d z_hhw;WPoT)_qEZl>07|PTfjC(~8}8r@=F?-rxt3wf856y_9Sm>3^yxNTZQG>iRC(%En|pEn8|mDbrU~ z3Ua#0{P9+R)G<{_1z3=3WoLJ77`*XRbeXE_)2ey=s(9a`m*?*Wi5l$sD6R5{wC)x} zfsia?HmM8(9dye|X{7^>sm219tG57>><3y*=_xw4sS-C>r;-*sqBSEz=GpZ}GZdzU zA~+PUezy}=>vc^wb~yNp_n}M%`d;AIX+hNcDhc0;2dJgT12qb6=JdmcAF(rj+Hqy( zh$?!Vb;{po|_PZrCFvf<*I>q)s`|28~TM2s~^{8UJ=Ua(xKfz9_m|@o` zJ$q`7n=1Y)^8S`}VO2W3aiK80bGRj9*S}%>G7sZbcw~XY`kp^GqZNj8Xa>4(jMry| zXKU+o;GH*W9YH$W*f;F(!z#&2&`W6mMjz@XqKLEA#F00A+35=YQC$W=kv!IT zrdxnI^qrg}tr5It0Akr%HcPUBSmY$DGqEy{{JV_l*F9$yaRk%j9*y2wcYOVG>u%d6dB`8vAb=G$gT1XpmN0&Z25=Q6((d@oGwRiZ)Sf|nyq=20VA1BNP$K^>!;dG&QuzKa>51!CKQs)(qg%B#c`F9BY$V`u zR=YdF_&+>duI#+d_iVHte=GUS|(Iugo_b95nWljsi!1mVoaQybc!_hYP}~ zxzXG^6U3=dyy0oO+11<0#+Cu{7qz6fUDf(#WA`!z_$jKrcaAR)pPrdbQhDQ4qe3ny zIl8K-4bsYA%cNBEw)t@(-QKa+pE;J6y)VRGT)EN5v)X?Rc&F9SUwwL4z_zQsis6fb zMEVQ8kt1G?bfSAsjk9{Q%L*ksSx03nJX^(E5)%@J(Ro+2TF zM50VIs-n-w3bb>>&e;HC#`Xh>@7#4&Lm7%k5tI&Mfq_*<0^#25w}AUob17?>Bg}~k zTtTmhXj9jw%tMmumXUJl0(mTS*Z9whF7DBNIECVqZIuE0SlTs+GpC6=r5D^vTnhj=AA86R5%8x1lcue+ z^_0!Iq9l^f9(w$pV2qsvohmn$O2>q9Re(;hb8J0t;akcW$5{P}pR?iW4s6UbzW5Gr zoEIXSh_^TW$ZkF{{zpnYd!(nR)rW8Lmr!-8tv!GFM;byg<(+WUB(B(hjyN}q7Tu%w z_rK|ueJ)buA9IfOW>8qI;(i-22t_I=!WCGstEvwaT&r;@U62+pw#!{x@Wm_i7(abT zHtH_t8J%SGrAKneSEe=Ejh~M$a|y?zR+7W}xBD~1_x|l9gPWWJpKa|z)M4!6*GrrI zV>yiWy(AY`OJvc{#OWAaqbE<3idChMA{R8rz0P~XTLL3g-Mzfx(T?a^Pu`x3*lgsf zBpvqM0>q$31^&BI$G3oyq9fFe)HYEnf*An7xB1YJ(uHK8a2|kD7F_Yz`mY(nK95h# zq%|9;#7X`V+}r9G{)ujk$)2G_w#w)5k_hi!Zmh?>vth&!)op2D=O;kmnP;u>){@<8 z0+)d*HWhX7u@5Xd-w&Eslk~!Xg_KIRu9X|drPIKk+VfL(-Z~08T7C=ob^TnU4ch`4 zvK;B(13z^Fit=(AVKS1Bo}XWTAyt0~0H)(D&>G&jUBAp-ZLQQN_fdYklXp78)bNnK zsPFJ>FiEPiH}VxXPL$sV%kyv}cQTHygexQO>-n0wmjpo&b3K2l59f}TFA3v8ztwLV zA&!ZER=1vvotGW+j;}>&|@u`vAc4O+I!k)q*=#FBwS4 zdXA&G3t@GLpRIGKwOHe$^5(nSifM39wMgrcnYGc#>@?+F74aw!7mVIOvs%itoTP!# ziy`tyXzmd+Bw4P~1KXCi-JCUI2gv;M)O@Hte))ZD&HGX)WJ|9&sSgsN`570Kb=ujv zG7)YrUwBuzslJ$zHaM-w%UL6`p3czf8lVSe_`J9)O84v>@~Wd&Y*3^;CEN?uGUb#M zNUnX<@X{J3CqnI(mmHM#D&qR3yxC)9)rB5VFifP3D?|4#k*l(0HBM3@B2ltg(|Wz> zZR50mdwRrY@@Wie$AosTA;eQqYMz1fVa~WyM^1&Fh}gA7nc_<^I18q0F8)lln6;``i!~UJ&LUt&0M8^+b?#qK=uKhb$+JQ2dpQrAB^T= zxLRJ(`1Wlw)fJOAvo~FhT>Ke36cPpMCU0q#Ut^~iu}(9xw1EFtHjl%cVaS=De*e>caH8m zEUG6@CK;;vT;uOt)HP{q-U5hN&t1j+Qf_oAFHF6vChQliT8ANVhXpcFjX0wMYLfSWoC430t&u6}5?9MK+9^=kUcOroZ43PEJm2D8@`|-q1`C-M$W! zs5uf4(bV!!$4{RMK~#|RL0_P9yeK;*-~$t?*G4`lp3&>v;u6KRojGyCf`&bdgC==Z z#8PKlD$XZdyS6lzk#9?-=jRoO^kRM;U$W1z3DaUysREa*^lrWj7J+yanz5KqkXuJ! z?Ln#1)yK{o0I1+J|jGk!uWEJt_b&!Ae5X~n-lHLV~etWbpTOdQH?7YjIOR=cp-fo0pS zSmfSMUM^L|ZXbSB%60d{Mk@W?w&9vC#n)e-dBqtO2`5_l^(wH;r9Wk=m*$WnlA<%c zQRZm~Q)>$}IlFgOj2ImJ)fyfvjR856JDWZ|EZGFfb7N;M7A)C_xT!XOC7;4jwl_-e z@~OMdftUn33fdge%KuQmto>{#Sh94vuyfqJDGgzQ0cXyy82dt|rS-FbyvXWX0Eaz6 zaDL=LL0FvX8Zs{SxTnqiEw&d>z)rZ(e5aXVCJnyYkki!2&(7lT2qrrs=Lj+$REi*S^}Fy)uSv{Y-*eQr=nyNRbfI~(lS-- z191uBpRKJbM{ zR2#0d$UCK?vjauz*XaoBalg>j0ql6D%47rokCAVjCbZ(9kK>t66`j3GRfh`4{UTfQ zJR(81`kPBm5zxCN|@LTmu#P*N1+C<3d~NmHn-);6!F>EMHEPydB;Bu&G$&^|UcKy%Y2wTR6SC_e;^)mSEjlOjGgk2|M3jqNJG*UR}&KIG@ zHbD;I4*r5SmDn1vn#l7c6Or(cFsf=msT^sK;H_`hQrmNsB_VC5zdkE$TVv; z9Ut<#a(v!@ovi+v^XFR;nQ6P^T73aj%-px_qZTmZ?%elHzrwmhC)>W_CtK%!yqdp$BBv?mvlTX>Pr_I|mx<*s>DyqVj6Xv>WYFcHp8QACU#ZuQ6 zVogJJ_QsWyc4}LVxOK2Vn7_T2f7B$Rfli8sd6;1QmmDjp`h-F^!F=bB1&MgiIPavf zIyY40GG643euD*duLa4+(%!Dkv!{bGFIH4H z4#Xr!-($>dAP7YIDIj}8!Q!>pH@&YS8NKozq2#Cy^M88>5(^+ew2O32w50m01H~M< zD6x(1>ony19(^zVPGIAFbtAAkxXllHKo|%!%9xfkNt*i>z7O}OoWBLQ7oDnOW|C8D zEetN+=t~Y`u99l5hj5w|%sNimUiG?~R*bMK)FAT~)u@7$W-+IbVg?REkJjb2A{piU zT(7;^&&q-J0Bm#QyD^oCcb&cQ{K1x8&EOoJJSlyo$QDra)xXRUSO$I-#+4t4BdSa$ zR`NmJk_8LYC5o}CquBCwGiAczlPP}uG_~Q*jm(jH@&z0A0`OQMyln2^6qb)(Mj zs)tfnaWX{Up9SJ1kf~yd;0D(X;uo!H!9`b5(Hg4l0{d@Xmjj`OQVO*`ziwuLya6@3 z^!s)nPOTZ;$T(baxcUyM20u%cx8}g531RY!BRfWFEi+U=JNFrgyAcg zqS$Dhnu1I90r(rCdFBOA&=4mVp&tWbpINFR^q4hqd_iV5iJz%v@vm>=e|7nDts*8! zUm!CoLBARjWR6_({~p}}N~}U5`!@H7`}IH9jsbR#o+@kM%gwpv-4r%s%hza}Fg}fu zmw&f?7Xjb7dhJu=<%g)*TiO>G~F4oPqr%#PEkx!9kIJu1=J%}FtN>JqcM zoEyHrZ_M2%!~3&J4;fZep{HA!4of+IlU_KoS-xBI@;b#=@SSGi%|<$PSQI zV^Q1ueK~U4rZjF*>OOTyibIb=&PZh1oOYbfvwrT>co33){f>5TbxIB;N%2|pV{!Eu z0Z$Qp^Wp5+!7SBmLSB%TAC;eI_f;}_>jC?K*}3;<-#|4zeJYTgIRG=)F<3}_+sEb> z@DAKzaZXP6lOp1MNm$|BnB;|-wi&iiou5DT!TQNpwQ%g#p^~3=jC|moC6LFzv$Exn zbUco0G&!8hQ{~7JgksVcuWmA{W}X%-S0$= z{LVkx;?S(a6A1CEB`H@zc<7&r`?eh%sae)`HG;-4DdLbmM`8?_pEVY)mrm? z__g1;xN+N4@;9RM3Wcij{{T#OD9u z)>~VJcCR>in7n*SJ(4BeCEkA(L|Q7l+RD;J26wL(^W2A8!-wJYTGxgul5F8$0xa^1o$?;g#zXUTJ{Wt$i)SKLz|2Fxq!OlK!Rqsy_Yg!{_F* zh*qgCN|L$N)5`Wjd;p$*$PXffOn(fxKWJUKC5fEnlpm82$XtBKp08bh)7XMD!CF1s zfImXRs1HBTBs7DRl0=(IqeHhS+$cU(UYkdjX}M-Xdjk^>$ZUws%in2W8Vc@*DY^`{ z0ImE_83QOJ!V#3IUs6H_MlWr1+cBp%o>^*FO5#WzrHvFs+G+I- z099F6;MfN_RgbSmq#^AY*h8>At5crb@U>dGvz*gI)h%gFewbV40`+St&&<;@%sEo& z-!%RkV6e7Na>1fWTfuF7g2MRpkEL+?NJHQe!TQfUY}8|&Y~Vt1psu&9uN6YS48L-_ z=-2HuEWTSj=@41J@11@wrD*Xk#pH)~W>}?w#`L#LSs0ZQ@Y~~724()lQ#NbKE=dB6;3>RWkwdrruuiH6jDLcmU@%Nt!=?s2_ z#J_3O_(Uq1hzcH5vf(t8RFL6dQ~&5yIU%XD>+sego#jWYCfyksE0z{Pl3pY?S zwOCDJw-_sxyku&5(q5n$UjBYZRI`Uo6wzCwxFW@;{?^2uCB*VuY?1Gu784!q(9jxI z@_lx$9`8}rwh4lz70K3}R!+#jlU5Ntd-?O?vnI?x8`i>_?AM`leFx&%&O_+I=|(Va zN1u|M3$-?(aZ=sL`vi)#mz-RkgzMo&G?BVs|>_`RXUg z$h2yvZ0O20&sT1<8kV2ok^ibb0`5q4tgmsla3iKd@9C(q?~&{Wcbt0qZu1skN~7Nk zUxrmlEPC6R_381P1;M3bSWSvVmEJ}iCIW)^?Gggz<&3DBDmy5XGkW_;-M1+S>3*|m zy~N(5!mr=&Yx5KPfo3>sp-fVF}$(niG;GCbs)CsBgVtX$ExP0r@60>ek_RM!wUZp#qAD=d}CGm1=BJ;WXPbMfHWT0b&s@?4s>$&7pzP@vB5Z{Mc z9(7?9qm z@em*LH`i++^WO+v|JPSPfZ&BZ?9BI3_6Fq8hO0C$sm+&uFqJyD-n#F#Nquew{k^e) ziHJ%_A|)+YD{C)yi*+D+?42ppCL%74+F0DI<5}bHX$PmP-@MBR*i9T1(wlO|Yru62 zND>R5aVnuVkF7r#a19zXD@WQwU_l-7BkWCwz7{)cqSZ6P=7sfR6Xac=CcjMmZsi}| z{{VCbs#5PcPqL0ECk1&oe%dTZcJ%Q`+_lAq&P70ZG8c@B_*xI61*16=SIu;t4Pw_699- zrjfDe{^<&^{VBzXMeJQRLQtFbw{iQ;bYFgsa?Iq}7OBW{mrJGGPRvEii4EbHFKELy zQ=^qp=V{B_mhWglE=W$Z=I~4^iKH^1PF9O+U@o{}=$qbt<#FjT(=yA3&sJtplw>^i z>k<09wU+OgUMoTLO9w?%$_?#a-U8y2Bs?3+&E-V_L{;a#PW%7t#w{p4JNiUye zV0iMH+7-Pc1OV)h^gzYpI-!VwR*k7wUW1dRy&gKm=|2!j1pbYUj@ zDip?sNa*H_6(uy8XAugm|ePn)I}ytCAbvxP)|fV<-D9f zY@6%GE>d6M;gN^APVDXYm0-cO^*$O-d4tm}6cVM9=DtsKXt(zae^ZMGx>-=9e-K_~ z`!Y^bai3xMoZ%DJZgAU@Q#qux))SI0%d~Ry;Sbkw?{eho_Pt-WjdZ4b;#Kxl5((Vd z&MtmULl@pr9A2}gGbHckd>J%$)63;+W*if9<%!>CWGc9DyARKwTyw5lr0ERQfipi+ zA!$r{C7%63HZeuKWmE+i^|Ql3=nBeG+z72Q^fa$pIsZb}vZN~O#o_Ob)>uitrG*}8 z-JGIBHyFd7k!}=S@7_H0>kc%y7opr z4LrHz$VU${s?i=p?x27p0D$i1v!&(-#douN1e)bNnh|5kH$p9SYXOV<`k1;oGwp`? z?ezD9ERKp^Nq*tRC>%{r$#sUW;H2URr)=qkQ z)-G3CCovet7fofidf`_2)5G?vz{c(MF|6*tt9e2~sEP$934*1^BBHLhp&;S${L@wbl@u{jEj91H^dOWfHvYR;nqtKS*GG^6k+S>aw%A7 zr%GI8paUen)z0vWc)7IA@|G2ZB%Ue1ZJ2m*RFol4DT&2Nm8a-IWT#7gQ{4O6V0sGN zr9`A80!XYC+}F;p2$6k6B}G|ujPY<|CsapJmmgu>#-@A)j2VDluAQuI=sc91mWL&7 zvuA8ih}-=f*HGbMmjk<~*7djx<39R3yo_M=PXmX%NULY6YU}L{eX7!&7it~~xBea& z#5nOAZUx}w5SXjRSk5eLWwoHHFwL7$nr`qvxkB4SwRqBymCjO(mLa zAS4M4fbAihz6GG-Gg1Uq3&jeqrr9Dw_E)r!7WPDOjVdXZPs;}ul2xoK1W*i z*%Fi+U7veBk|*Wm$+?Q*xVb9}Poq`1)@vjxd>=Xsd0@4Qq|zRP7{b&e)5Bvg0w!lX zh=+b~XmpztXxkjSC5X1XP}7|!lYXhy0BY`05q)ZZSJ0AY?0uie?>C0Wl7@?Q@uU+z zW@T#cn4T2rb|3Lfe>uhKS#QE2DmlbMLY zh*910F;9sml2Kfe|71B1sO3F9WwloO^4?}iB3yJd+D$3*cL$!k3c+`)TX)RQmdRUAeUPJ zvVQ(yeg6mq% zs$5=z<0X-+beBJAw`oS^7>27iW}MQu*ezD9LG7Nd@Os0XR+RLgl7iK*JiPP9RAV;a zG-&Q2|38%-ojdwKXz7}(2+dzw-?JRn9Z5oPo0o0xZU|^At?%wLn1}b7k~n2DfB)qIWeiJA}tFM6{$e7{(0TjTGW`gZg}V9x{9-^rizWdL7zl5>Za}I9uStrC#fA%?s-%53kqWQOX=I+p z%B|QN%Ra*;j~?w*Th|B=DnC-e=e*|PWtk&IGIhhJE7?X}?HMo0-5V3_+Vyg3c4jJ^ znHv5{l7GAP{7W-3t$ncnU;lA7`Mz+CI)c}_YQcxn{{z=vMSoQ zI(m!q13)L4Aueu53yNjL^~s>nohYkL4^TGlK;jqFGy2kDI@PoAC=W3+oRgXMwJ&;5w;f=U z&_x20+Tse#?)y-oo}&Ylv&&}8^FXwi<(J0zIGh!gc;WZPQ z*MK`l?K@wL1huq6r}nLrbMv<>@Km2^6E#0DG$POoh`a^RPTNiQU4|R|guH~3p^7dQ zZDuVE`NH9%GW_8XBwb3P&N@{V@HJWtbR_Qu0CUE>>@W@-Iya-$n1g3G~0X9%{HY ze4!dWefSJ1@{%avA4`Zu2x@vKe$vjX&L(FA9h)r8fSWo6tQV{uL$7tmE@z-O1JE(s z-<5EZ@fevlinz`xWA1-Fjm+bo1?M9{W>5x ziV$P@>j1VTm}!^C`w|5#(7*~PTsHsE!;*Zk?O&O^G3aWz9pkCS7@*J;W{2exGDOeZ z30DseA!U;qhp@)&c$&3#bx#(bR24Z^jlmd_E8*Bnm8_EP$$k?XTxdm*-FDIwpTv41JjQ_ZBKl-fxTc-aC_tJ^IObzcxM=a*oj`XNh z=m}L-cO|{kQ8-$w2L$)VzzN99_@n5av5J?tnxr_?ab?)SvuKpDS6N__33tSE4WcE%<~zuBWmHu|*`=WF$> zBB@wKDSQTl%(0{$1XgK|nX)mDvN@>AIvG~Ih8hV@&!5LlRsF)Fu0E-$%*7~cwY*;2 zs6y8t%N$22sv8dLV0;c6;(FYM(712>O)mM`LdKJ4sqdlSOaKO(qh9+n8zDtuaFsiU z{&XY@>xD$hRMib^U4Q06rtlE}4~&k74}P;#bUe)V5tC&eLrS6HYSeEMvLlVw5q%H( zgNEsZfn(7T9$pcwaXmT7)(S^?%-7N~uWvAUT-SNpPQ``TEXqbF(9>L}92mv6=}H@T zwsGtwV$;koR0f%ML?}Od*_Uju%KD@(5sa|9lM5g?@9@2hS+>+=S3P*`vCad{i}NFe zTGbx{1z~= zcWkdFkRx?(Ope=5jzjpw%-lqEe3pk47ToUki0TZ)dGlfYu&;r=XSRc3)`VI@L zsia~GL`=u`LmKg!Iy^J8MfF)`h$(W%qRUk|m#YwzN#a%+!Vj$kOB71$9|xz7P^xIW zk2lFAs)&o5t(dA4*~ALG^3`czsEK^GD8;B{-(9p!;z5=}NvcYz68$7@0J@Zut{B^o zUbsf@nkow}U}XyKRqcQAgt(b4!H6mjwe0mgt2@0~m;L`%nO-<}FC=J0_e(z2Q#VB? z1Uai%?P}EutZdg);UeJDsyc1)=yB+NBi-h?olj#5SX)Cw;?U$$7MzU}&Mz4!u zk9o$RD{A^9*=mUY=K{P@okMoc1HXhcrMn_8&kTkdh!7uPVkTu^(fjbG)_5>GfhtfV+JE{*n937ecSR*mDE&QW&N<9?c=Sua7@_J zzGm4NhsnMWUBQ5gbKC+eYloiN^U&A`O%c>$OT5V-6UX`mBMO%F~9^PkgY;C?5HIFv~!rC zZBB11TaD-rxDnGY&iy?1x-@S#005vVVs0K9DtyAd8=AGQm{#EQQK!j@?bqPj^M{XH zy2nz=wmdrKaoRgO=!>tz+NN`7fbDP{>pX>S7L=!yG}%kKop`ININ8i1r*G?_Hljlx zr{iuI%u$Ii3Cdh*wHmZL_4!B+#w^&;4)uc_ofU{zg{iUKx0(IphpPmdR-;x9*7wz| zyxw7m`2BS-9k8EWRJ?oUK;_+G?)+Bc=LO4{%82pK!&?-+x9=kxer~E2^U|W z!yUP-n{)g-a)k6AqMMnR-*Kum-WNrgL$$u0ubhB9D$Vknua+qz1b%fnKNqqds6=RG zSL8jJaY(I6vG8JI5)7fRF!FSBig{9WqwUs5Rc;`-ao8ePZPV#Wldd%5lwol1oK6QR?q=+IP?Cg^IOe3~_Rh!8NM3DQ@m_M%v=S|aRW^%_ z*w|xIJ!y1o6-4xTtIC{P(-72opfI0%Gp3ibNdqXB@noJ*RH@ZC*Z9cmR7X8tVpt})x55#OheF|8qU@y+LTw+@4AM&0RVvLp3cHU+GdJ|5)E}>aYtQH zcW=#y*Dvw#ayMKl`iLLaPH&GHr{b&X+T`3*agNwkvl+*(_-#AF%K)IyPSDzx)y%T% zv&*xYJSx1=mzeVBiSw&gqYhGp-kD8fq>Zi-^GvX^F&_as>-ckSV!y`xQ)TF`NU%{@ z$+OX$3xMV@EO&8z*@ktXBrnihu!PbGBcRb_uS&?KJxW0i>|>TO?d{7Jo<|j_>|?eb z`fWUZOJQwYc$Hl;iM1+$@gbU3Zg|DGL32{NkL5&_h~gT?46VI2*>>D?*FAqJ9B!}m z1Uq&v^NA27@V%}-$P0}d&`9a;IjV;Em{Kd}wu|)~a{Irct}3k27c}rX=`Dq|7(%j> z{3>)X-s9W?WRL1sv&fetT1N-sYd~*`XH)qIjbKUa9aGpm z+xLdjpp*qKLyH0c{|x`jde$A!cDFv357jbyhj}blWw(IzHhP8+BFcU%uP>YCU4)=R~5b>8>5AX+64dQR(xYr9n$#t7+}p z47K&EoypH{%Qy9Wi;iqWKD6~CK6f2ZI*VgG#$o@Y+R$~YuT2KuSM0O8 zBf4Syd0 zD$zycSA=3b_L>Bdk|eJ0(&Cnp0Y!8h=i=o_updql$109*oR8TDA`vRt&dGK{KgYWQ zQatX|+sN!%#hvj-S(e0Cv7E4-S^h@>=y*i`Sr%Iq(1kQRJa9`f3>F2gE@fYvL9)@JVA`P)WGFgFc)D60mna@S24$*n zv)Y|ZJO@=DtScWqwl+6oxPWNow`P$GvVOW{e&C^?NifvJ%gbqow0MDu%AsDq>f;thWGVj5`VRh&#urwEYOyazk~V z2}S?)PZR;+LACy8T6Z@W446XSFc~b~KT2uom){OnSwXk6?4fys7J}zp80O#N>$nH|_ffaW2>-*nFMzW0^5(Jc?a( zfXd4$-bgVv+A2^-F)Al=7!lg7voW3nSJXs-y1+urn;_B36Ty*L?3|T%5olZ9UJEkM zU*W;u-Mpj?`(tx%{-DC{kYxZ{Zn$9EfVpeAIKgbzf;B&Vv&}Zhd99b->kuf|sxyE0 z=stGVq^%@VGyT)XCAfI3POU{vkG$0TSWk(X7B%vj%CxR(3_Qoaj57(MN7?Uwi044# zZP4Kw>pUMc<(+5BR+}m>T<|P&lSOfw2A`&eXE**x@ZgThzkh+cW`<0n8S_u@=Yd}R zAC`%q?7~i%?F^0zZgj_dm2LqcO4ilV==qfhHKv>3bk@RFt^ks6K%2nrYPOylFw1Jv zh&_Y+nBs~mJMf!`s9jIwA-jT#L$s_~st?bfa7gv|n4ZMAH%fIYt)3=PI0(_J1aGYD z93zcLLHjg*E_SvlYwuCh)fbf6qnjnax?i3r4l1kq=?=EP5#7oC1N%H(CrU#2xq{s? zxl!bd(F?SO{(9h!HTyK2yjf_I^;g@qx-8@qx+sSQ(V=?75}CKAxYecDwK6&B-hFMr zgN0i_F3`pAx$o{R;0~n1zgq4%s8)~VW*nMPv1aF4ek_rmOW-T27?&W{{ri0F*P@Nw zY0J)uy(hv`&f~p;n{w*sVyi-15wVyv%Y2YhNWn6&rAH$&XCaHd=74`rae;EX75+1tNoi z9H7miAe?0*J~?Cx+FB<2ir}=Ph^>9~15|6ucsU)OpFrV9&7&r3NGk4#dmb^-hd40< zDg9>u9haO&vKiOUW8E`vOD9B99C&)heEGwtQ?__?-#kx#ktNbknG{#QXpScmo7=eQ zvXfiwmA3F)EgNtrrEQRas^P(gZi&zw7~BhV_Ut41k(_vnJ;-3*@J^|KC^=R=qF>T& z;5p{rv2vk#K0+XCB52|GfovOD42FQ>a`cD!y@Lzj2tn!ZqkUm#UdgYY8c2=1rR*Vi zHl?M_<&E2H^>>BM$!l~S+T&*~0xE+bEFHIi_ELAP3)F)b7-YME^z6ACYbjfx`@`S< zulAB}6KR|Nz5pOfZPd|azbbBBd$hGGNL!?xq*HtF>nC!FTy#Kt7lrCP+x0JLw9(|Q zzY3po>kN(Y+ zgiyqPBm+iJszm(5J`DVW>fiFZvJCwb$H$yvW4)S{Evh-7S-g0=XEgb!IrIl1;40Zh zWktuG80IfC&{Y2;{JCW_1-FYvc|rcy%jpJ`_ElB>#n)TF-7mk5nHc+>nh;}kuT37V zXjSHv$qHZUN_|ruCB)0;U&2hBhC7hJoP8(z#JOFuUlB)niZ-n~U0=SfGBR>=ZpD97 z9qjN&Xre|6-dCulL3ocw{lnn>Bl($2Kj@j2mc<5t^hs&*6&f>I+HfA{0plId2q8A6 zW(Vpe&{$t!Y!7qIwsYTOt}=0@)x^&i{@c-YQLbG=>0^S%R%392S0!qS`3hnzu@33X zHIKvMzYSKDL@LQQZ2l!?hx|iuBw?Guj<-Z_asnknmr!a=9VnW?dKZJ+$#v%>4?L(I zWmXSK%uX**PY-9xk^ZeVd&tzuZM!utWgLdYA6OmbTWKh}Z(3{t#Xhm#U^;R~<1$9Y zUai_`qF#m1EQ|4|kply@M3pN2aO>yT<;2wUc(@ogBXrIb`n|%VUlI~$yQ9D$l&Kqf zi>4I}R6eS+Zb$sgC)HfZt)L^Bk;2t=Okkly=xXF&J7`Jusql=09O)$Ve4@FhoFaXu zD`#m@uB&MdV_F=eYE;wvBh}h%%cv`HjbP9fKRS^AVOUFuUh>Dg6M=|l3HjnFV#_L0 zT5cxSvJe7@vOeatq;Qi8y)J3eVZUz=xex}yEg&2BvH|_6iJQ=Sd1^d+xDWOzzaHj0 z8uI(CfK)O7C9_Yjj03LcrMzEdapI+lVqJA^t&UvsGv)j-mT(7}8KQv#nV~T1q1c{w zD@A^TyF8|tT*EuqMYYG;l(69gs-}|fHL2{)!D*8s$PMY|R6iJ=%&AwCP=h*1ahBP@ zKDui_Nwk>+uSAH4qEOIARlJgUL-=&{0o3P_Xo(tm@7`sgb8zBfQVKOBXQ5XuUi&}^ zPdazogSAd(#=XY#f3)`z=#M zIs2Tm?>+ms_r98rXYu_On?0&-Tg2NT&6tmL)#TqA`B(Id%=;iPY%6QYwe?XLbkX{6 zcd7T{n+q6c$|~MERwjrf)2&J)wm9iFrT?9mfj4opySI0X_ckPtqf&Xo*>+pI4%!78 zo?Y6EJJz+9fk5vdIcoI7adVr;jDJ&5(;cmzz6SMFWx(v&Gn2ipvB9^;wEre&8A~ev zG%Iuvdl2a(q}7B;&sh;#IreLo$Wf171TF`6`m$fifkN!j4D^GN>e)>@8U+8L!fZ*# z&CH)Wwg)M!E~@CGtsnzmD>+4fvsiX5Bn3md7+)75+Qtw#P3A78 zKmMc@ou8J*Hrr3D?R5zQwO}c9zek84h6Uo*(tnTWo2%4kcEpBz-)=I&y9C;|=fi|= zbnW*f>FJSN-#Iv=wY^yz;JdeDd7dfdCtc3{IyE$?d1L0>C=ICBxB4ZxQUatBF zoZTLGSbFsAv_r$Wj(Jqcc6E(2Vn?o1O)lz2+_3gLI9!lfyj=6@4noCTcGT-Zl6h3l zMoakG#R~n&3Go|^iDt{AWA?En91|z!=)07Wq@Pbjq0Sh2qZkomT{qx+k+?zFNq|`x zdt*)uQkU&u%MKoKo4t8$%!2~|)&6q*KD#~rHQgz=rJU<-^VvnCK*UvRD^hmcT?;v0 zJ_Wf0gAK=>`{$X?>9rv9)?|u@)b+d36e@Dcum`+vWXVw&w{$Byf^>p5L3i<_Z##c5 zDom#@U)s_~!vllMs9OxCD||;co1v=$4TbubPU<3#9ocQIFVJ4H7=GpU0N4*s%$Hcl zmcT?xSA?n84~xBoH3lO`BN-UIrz@tjhSs@)^{(i3I}Y`Loe%l7=dUN;5j)MW_M+KR za42J`s=;VFXYg5BPE;K$>6{brf)>||a5;eS;K3+q6Ia_g$Y>^_(pPGDLBxNa*nu{G z@nY&ywrjch@j(~$v}x(KbJ`qQu68piZlt-W_0jdkhK2o_0yV!^3~l&{M*&*$@rkPR z&;c2P9yAv1teT1&0nJ7G`q0w+;dhRsm4t3>VtmALVMosW7uNON3G%FS$Kb*r)^KA) z7e|rx2Qgu5sB9nWr*hX0Pa3^B`By*YnN5-{CdL^3o9`&lS7?XmHp04m!-4VEINd$o zyAnIpLV1PB5^l}4PS?~7zhxE1qh4+IRp1 znDE7)4;JS>hh6tFQ*!=nlyg`V@vHIj@&uaN)Z{)pBV&QZx_todU2n?C!_zF$+sU2lqB|P85^IQfGA6qU%z{St)|8t z41uEGh2QH8U;zpMcSCc9YJ`*McAz(Q)cv&2*uq5m`E@ z{A74=p)(`j=C1XMU7p!#jXZAe@IIkykap&O#Wz|E_?zzf6a`4x&P zTzUoU;~Y<-8Y@N5QrjBlSh(&?xFhfAIaTE^Rxemw#;oe(O4V9P48>JT)bP^%^c@?0 zgBz*u(q2Pub4F_MUYG}IH(t*A!oZ~`8h*z4-yQx~@M*`2AMN{p)QDCn8#?HEY#_#29z^ z;<&t(f-DV6Q5yQ3xtgnV>)5XKRA4g_fz%2P%dgHm?0d$}S$Htxo$3v}ZA`l_;$AB| zG}uh&JFqMdstd0KF1x8;c%Qj}Tuk)pXKaqT0#d)2sIPrBO_IK)ef5*gH#!Hw^9)}Y zfFXNqNBz%=-ZQWtJdJ?e6sYT5bz@U|%!*=&NN)(kM>yRxYNii~a5(qYBT}>*yQN5J zvA&1&z7f3NDDaXeM^z;$Pq#osK0`Z`h0mEb@ak@ zm`D%cW-&JZ>p5`Cc2ci;=`b&&3pLiYzi1w@jcd{|K}STzEi{)}3cIL*INSC3qVm2l zAWc2>T_H#ztdWg|Qc|9?l!*Lq@t{&;(B9a3%Obl=?`DpET3%!VXj%g(IA>pqNqoTsqMNL`LhD_v>0uAhvh)Bb3s z_rMDAxwWUVs)1(5h`pzk+rW?7yO*<}q~n%YJLiPgPuh(%bst5IS|q0!x`xO;w!j(m ziDbwmmOex3ePPgjD`&v$86WDb-5@)d6Ulfbr%XxQ(a&iKK?L z)ub|{76FpLUe|3BvcCENtfoqHIQg{2e~2#x+t+DXaidJoun0k&hqu2NPJ4Z9B=Wz+ z&Oc9oD3?!y@;Sz$bNGT$k)=gcUYTsoCRrXyufSGk>x_yXbZFi*0XzN%b7QJ5^{rh6 zXAM4$t zVQ@-ZO+iC^G;!D0Yh=MugP(592)V(^nuvI`tD{_O68idwTDb}KsxyXnaU7lYj!f1s z<}}VV{i=3kfXW3ye%=qQiPlZm5fnJ43uir}X4ux^*1-b4j(qq0^e_Xz8#&sgC-wf^ z8NrOOg-mS}j!%5w~-FA}^+< zb+rfCRWHgt*4unBwkyEnTjNIM_IGAqH_9&A>J&6`YfcWjSmnKx;2uTUQ&y2B)9~4L zM>e7#*lmAhxYP6vtfIqv>FR|XnoZbvViN~jf0#d_yAjkdcRLUV-q;%h&4C(v;y$Fb6&gDOf)&4)P?49(oz zCwvt^AT=X!iXUEruW5ab(1$B{L^G!3scn7S(Q?lO#6ySR`UW@#2~U%8zR6U`Db=5g zVluk*(}1Y=gPVgN{$_RKgVU0mzk~_zXEVT&9u2L+U03YukvOHHaL?I1!BQHj-XL_g z#2%Im3gHfYpz)4@L6LaoJx@$V4~nDwY><|RaDOtWQ>Dv5Pj+%*50KpNbf3R%4JhymImnW-PwqxaJH zvA5WxRAm5`HQvCes35x|f#uKkyqIZqglCm=%$ykIKRH%^-9b%=2AtNUGm zAPq<^i6FzbEDc{qY3DCWz(OUrUHqjT6#{2wB2J7SD3>+`Em%L?P_!?p z5Ds}ef)4Tvxld*mB9zMT-1=vS$m4#78Y?3NKBqhbudOWkrBGMr&Bw^17Tu`+;O-90 zrg3p&^n(j!0#Epu+B{IUPxgEeN3mUx`#yN2pK}Op*4|&Vp-|lxVh6g(x-XbuZ;#VB-tla~)UfTDs_8nNSqNrG|_W~T&d#`p$c%}gy;zutOYMMvc@1!7~+{K-(~^mT12LEy<%wT81XO_RxeW>(W<^zX;7ji zJ49!Fy-Ul~lYy(zC9 zX3Tr(5?hhKYs>9W3C&|WbgB$D%PxgFj<{KRY%Xcg?eugd33W1W&WAKH?sGgN_Ovsu z3mFL9bBcuF^ggskC$iMf(@wpS&@@7p3gg4sjq=C5L$ke{XuXasH`SNaHnXR5gW>{t zPtsTmj+o6hT5aflBbSmjq@Nzk-wlyd3fEa6*e5L+rl~00aB9p-^1Y00W@zg)_oz|P zcvM3|Q?jj15Y8ppht`-LVRK$2^egdiH)r>da*^Gw_EFc8_pj5TU2-4-&L~oI2C4CD}EH z6}KNvK4J0Cdry-uh0dgV}=W;5}il->9 z!eA(LoV8#IiIYj^G{T9z&k#44mC0E)E)mZ)eCV}|2RdO6t=y4nn`9Jc&D2#4^o{H- zhGPY_F7PC4UQ`msph$l;^JjyIj>y`yqX63lRHq&jWBP_@N3(e>ua1-mJ+ip z9Z&3zxc9Mnel6gTy-`)0F)CUl-2Bl4YnFer{`@+kf9e>(!0__959)($8U5umbb4WVCUa{~!Q$ca{h+2to7i*)#FL^ zPcyVO^4Qy{@~-Z&({6X$51yiGVw-G|Q?cj!pT_T8(DP!2HVKJB_=+xYtng<~3eupu zCc{bw{Nm#(D)pfKLqzGDfP7^eTu5lp_v*eg^ZQi!z7YO*N)_=&j{E4{5fS;&1O?;J zywYT7RQ9?fD7^!7!E7SNFkjor?yDuk?~Xq-$YCizgWEqcJmY299aEm-3dQ)iI!}~r zVvz4`xpi#&d?-@fW29 zt&Z%XSS#~t5`$FGHN9CYzoV-|ZqA7y!_EokhlEHFDY|GtL5n8&9kTfz<* z6{~S6qV538VowWSBBy7GkMxkbIr>wU-@s4i<^7(VJP9)a8Cpm|($8_-V0CeK!ggYK zs;;a$A9-c6b-25>SMRB4BA!x(PcY|H0?3Pij8hh>GBh=PsTrd?emrKT2@39T8&x6% z(>?A7gK|o5Ub*kqniVv*t?g%-$K~0pVnVj4dzU<5%vw;+LIz8eCGfNj7zR0m_B=eS ztivJe1D~aqxZT?Fm-1G9s_i{k&BDU@w21C*OF|V`%!b9hGeWFWeux9TMmR$jB;ONC zKt0QBwxEN#gHi3i zGlTdfYXg-`9ej!C$&HR9)-(sby1}0ChS5;THFK7H&Uy6)htUXr7 zWW2Uew!5EC@VO%Vf6%lEvz>l*pKZmWak z?fVu-O*9#i$LFO5np=`^?jM3JND`9tob=ImSzM@7gcAGaE_-g`?JA6RmS%m%@ODAy zWLr&>jdKF}pw4f{Z{Ih!UZcxy8N0>AYr97`{C23W9G~+8)f@ZDU@qzyA3lg?IjeLc z7TaGG)lTX!=f%XkQZtAi7IM-MN{S1DV#rHya((TkL8GU)NdyqHDJC z$I0N;C&-?PLB1MQGv~FFJ}a@7i1DU}B;x}iW# z;i{<%>S6T2zm|^iV$NJ7i!lD^sn45~xV*=Q$>35~^&8qqwfaLH1%DZeqE4kfc_hXC z;^cVgDx*VNCrfmHdvU3@xq@c!#_%Lx~aIWa}OVznfrYMwZ`ouzZ0 z1+VQ?p8@lo2Q@V2IT6;%$AllYA~p`+bmGCDvS1NYCaEW(*&WD%58!OMG5{d*)*#sE=RrMEs!3y>fH61G z8#ooCo9zdi1vUY^PS-G5=|D?$?&YJn+}$-BAn@S&y$y9;43k~Ci~WL6R&TJ=yp?9h zl6P*wVH!2NEEFh@rQ$yKckW8$jL}p!H;!DBCX!xXr0ghew1bG`1WgU_T=I}UJ&Hj# zzG%5X`e1&8tl{5CR~6cTm3bu$YPCT=Z-r-vo)^KvM_vF(b$}y$2704sqRDHLW2~vp zyQEn5v1wx(-$SmEW0 zBfOPfDzuKXl?lN1n2P<%5!lj^&Sp+u1qmiAz!lpk;tOvO{^XOr*Qw~?9yoglPK2R& z#d20BUd+dKH?2NZLDeX`cbc{puP1Pu7fZyuicNF5JJfE39wa2bs1KO&+k3d^OYpI8 zJH8Q7KkoYBTEgq1!pdlWt`1{XZQ0pLgWjv#T%rZ_PNB}bL6zg{&xYt`jyq?ni#y6r zA>67#^;^>Z4=kusSz+-Agk)x%yIUy#K$B2f5m$(gZyQ(V zb^Ar1HnCjqFdT5VlW_uA_dKK*3G6)3O0)*CCAv#8HIDuW<*xRG03TG4OEvWYFuZ>J_Z0BazXlYFgYAsQNX! z+qt_O!4>3kw&}vj$-oQ3*ULHctPur@Vf5+Fv`t}!D&m%J){To$DuQPm*>%m|=qH=U zdb$IMtGy(T%>fE~XAQx$5CiDi+4UPTe^mdVkJm}X`B|2h-^a;L`L5;OCS%}?OiA`T z`nUWNu2mocDV9`f!Swk#SkTBcyQ{_pkcNqH6}DZIaIH6!(C;+A*6C$a|7HtJoyxNf z=$P@|7UK)EL=-O}(K}u0hMM~U*Q!2SOC%Xe@N-FZ`z*{jz9CuipG`eyg*9Cfj)AfE z8&SnY>v}xJQF4=G5`Az(I5=QQv0)-nB2j+FmD!3Q;1bY{#izB$w%;x?gy-iD4$Ln1 zz9c$l&&^BP#)MAL@EQ|I%5O%OR|cUy4D-@id?l>8M)TwZGNW}65l<=@EE!!VD3n2J z6Wk^0>eh5zdZWl=ySe_(ciCT6dcy$eD4|+Gj91^@-A*rkIvd-j{Q0jnkc$UXqgwmg z4RWS=lTln%i!FZrFT9+HBlS~A**AU_*&pA9g&#&RP3TOIumFKxz-|de&!E2x)qg$z zkL?;3N)Kg50>SwqNh6tN^Nz z3?KV!ckhU9v;`ZH?_&i51*WDiWoalh_3k;3@nCFhk9Yb(#M+is zAIy<;$JV8KCLx|xkIyi5xflc#Y}7w(*xStW!1R?wJ&$&r!31a?wH>0hK+9| zx`TxY7IiW&l*e@DENx2$0@W|+>LERTYJMo;=ZR$!dxfoSE$OuZ*AI5AgMmM)DL7hA z(?c*#JB@qy;&Bk?u=k_Y5odbH776qZAkGO0Qk#I$ zx6KQ239W(eJWdxCe<&goh+qf!c-<5dGZ`+oi~XF22uWHGB-}4cOax50+;uBnEF@5u z+?ooBHD!Fs6m@=@)Jl>fo*qNQC1KE$8dp6Cm!KlpUcfTIVjq#&;Hwzzes^2Yt_H-B zTha-IhliR|WyG6aKSP-w zLttQhDU8CIgmKQp`)c(@&&b21QP$^m5pb}E3`Th}lnNMs*B+>?FgL-vyyH+(1iQJM zPD51V7hIf0DfduuR*rKY0$sJY`eG3hG~ws?sfl+I9}jU(XIxUYiY++ z)^)W~O#*U{FjV2>u4YQ%!%?|q1-pH+7h(r`7ueU4NA(rH`L9n_+cWN0fEx~1-AA1Z z3d{L80hJJf=?ZM32rOY18Xe+kL@BLQaN3AZ0tPkKV$O72LQt3}!q)_I z#?=Atj#Qb-`QD#ul&o?JayA1y<|39#b~INuz0!+^_u0l>yVM-TFe7ZWbAbP!>p^7Lxhmzu-<@inBOKXU6(6-$C|Gw9FpQa z6eCK2h8Tth{Ea=((KqZ zjPkZ4MrD@Q1>0a(yG;Z8;OkU2k|O$&!bqQ(dr$M|avWc{-sOfS!|ZuU;JM6DUln6A zH9BiUv3>-qU^~L{`Y9`xRlyhtbiMpZ zvO}U$m2;@{N^^f(2hpKL(TsNsDZHGe(d0P<@6SGRth+K`MCA$`Q6v!Il(CU0*YgiY z-6K7#0zOAqYBk;{IM+>Ki?}+r;Y!HXRi-;_@goM$m1JJNEOh^%a=^h>O3|GT5_7@c zT8bF8+ZERiCKJ29Fzi)?FyX#_Z1OYQFl9;n@xsS{mE(WUv)*_dX7gkk3-;4Zq{zD-2xY)GZCR&^*UL#fV9;!(jpRC#F{4@iaP5*|*H<@E; z9=I8RjH~^d(qI>7xVucX^md?ZQ11`D{39W43LT-ZQ$t8R2{dxHD%MSb%85?#om^j- zsM;`=H{G*F*N6h8j2!L1A^D$X{--u5|7rXFz1&N`b2EnmVMps49O!+_VFbzoxvx(< zz~2SG`KIt6D@&WsVE0#ov|;vH%r=6fG?V<%-X`!a-;F2oB=3V-nkOsTAt^Eb-ZvEg zk_@!4){zrEHf#BjH6ZqbsKre)}!&B3@Q(gT!r_4-_;p zHmu3As$IxWKhX)wy3hDHb&g0kM9F=k3KGL~b%qZF)853;5c%J$`p&@b)8hNO`X7@P z2exQ=8WRYPTQaZ7GCk|$pk9~Y#{WBVWjdV)3!A(izt_=AL l!>{M%_zS=P{y*n|gzzVno{NB%ysI2l)NB5P*o7}6{|8;gMq~g0 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 56/56] 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 }}