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..a639b67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,264 +1,290 @@ 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-arm64-darwin) + 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 + arm64-darwin-21 + x86-mingw32 + x86-mswin32-60 + x86_64-linux DEPENDENCIES github-pages jekyll-theme-minimal rspec + webrick BUNDLED WITH - 2.0.1 + 2.2.33 diff --git a/_layouts/default.html b/_layouts/default.html index 210b8d3..8165390 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -35,6 +35,13 @@
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.
+
+
+If you use my referral link, I get a commision.
\ No newline at end of file
diff --git a/_parts/part2.md b/_parts/part2.md
index 4b16b7c..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](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. 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:
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);
diff --git a/assets/images/code-crafters.jpeg b/assets/images/code-crafters.jpeg
new file mode 100644
index 0000000..4fd364c
Binary files /dev/null and b/assets/images/code-crafters.jpeg differ
diff --git a/assets/images/splitting-internal-node.png b/assets/images/splitting-internal-node.png
new file mode 100644
index 0000000..8d116ae
Binary files /dev/null and b/assets/images/splitting-internal-node.png differ
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",