Keep the cells of the leaf in key order, find any key with binary search, reject duplicate keys, and answer SELECT by walking the table with a cursor.
At the end of the previous stage, INSERT appended each new cell after the last one. The rows are stored, but the table cannot answer the two questions a table exists to answer: is this key here? and what is in the table? Answering the first by reading every cell works on eight rows and fails on eight million. And the next stage, splitting a full leaf in two, only makes sense if the cells are in order: "the lower half goes left, the upper half goes right" means nothing on an unsorted page.
So this stage makes one promise and builds two features on it. The promise: the keys in a leaf are always in ascending order. The features: finding a key quickly, and reading the whole table back in order.
Nothing changes in the page layout from the previous stage. What changes is where a new cell goes. Insert key 2 into a leaf that holds keys 1 and 3:
Each cell is a 4-byte key followed by the 60-byte row, and the row moves with its key: btree dump shows only the keys, so a leaf whose keys moved without their rows looks right there and is wrong everywhere else. The tests read the file to catch it. The source and the destination of that move overlap, so use whatever your language offers for moving overlapping memory safely; a plain forward copy will overwrite cells before it has moved them.
In a sorted leaf, one comparison with the middle key rules out half of the cells. Eight cells need at most four comparisons; a real leaf of 63 cells needs at most six. A linear scan would need up to 63.
Compare keys as unsigned 32-bit numbers, the way they are stored. Read as signed, 4294967295 is −1 and sorts before 0.
The detail that makes the rest of the stage easy: when the key is not there, the search does not just say "no". The place where it gave up is the place where the key belongs. That single result is what btree find reports, what INSERT uses to reject a duplicate, and what INSERT uses to pick the slot. Write the search once and have all three call it.
The position is also what btree find reports as cell: where the key is at the moment you ask, not where it was first written. Key 3 inserted first and followed by keys 1 and 2 is in cell 2.
SELECT could loop over the cells of page 0 directly, and today that would work. It stops working in btree-leaf-split, when the table spans many leaves. A cursor puts the question "what is the next row?" in one place. Today its answer is "the next cell". Later it becomes "the next cell, or the first cell of the next leaf", and SELECT will not change by a single line.
The parser has been giving you the select list since the parser stage. SELECT is where it first reaches the output. * means all three columns in table order; select email, id from users; means those two, in that order, in the header and in every row. Check the names before reading any row: a table other than users is 00401 and a column users does not have is 00402, the same codes an INSERT gets, and the error is the only line printed.
In this stage SELECT has no WHERE: it returns every row. Filtering comes in its own stage later in this part.
One search serves three callers: btree find, the duplicate check in INSERT, and the choice of slot for a new cell. It must answer two things at once: is the key here, and at which position.
Appending was enough in the previous stage. Now every insert must leave the leaf sorted.
SELECT should not know how a page is laid out. A cursor hides it behind three operations.
* stands for id, name and email, in that order.These are the exact lines the tests check. The full definition is on the output contract page, sections 3, 9 and 10.
| Input | Output |
|---|---|
btree find <key>, key present | [BTREE] Find key=<k>: FOUND (page=<p> cell=<i>) then [BTREE] Row: id=<id> name='<name>' email='<email>' |
btree find <key>, key absent | [BTREE] Find key=<k>: NOT_FOUND |
btree find with no key, a key that is not digits, or past 4294967295 | [ERROR:00104] <message> |
select * from users; | the header id | name | email, one line per row as <id> | <name> | <email>, then (<n> rows) |
select email, id from users; | the header email | id, one line per row as <email> | <id>, then (<n> rows) |
SELECT from a table that is not users | [ERROR:00401] <message> |
SELECT of a column users does not have | [ERROR:00402] <message> |
| INSERT of an existing key | [ERROR:00701] <message>, one line, and nothing else |
The footer always says rows, also for (0 rows) and (1 rows). An empty table still prints the header. Strings print as stored, without the zeros that pad them: an empty name is name='' in btree find. Cells are numbered from 0. In this stage the page is always 0.
$droid --db users.dbWelcome to droid!>select * from users;id | name | email(0 rows)>insert into users (id, name, email) values (3, 'carol', 'carol@example.com');Insert statement executed successfully>insert into users (id, name, email) values (1, 'alice', 'alice@example.com');Insert statement executed successfully>insert into users (id, name, email) values (2, 'bob', 'bob@example.com');Insert statement executed successfully>btree dump[BTREE] Page 0: type=LEAF num_cells=3[BTREE] Cell 0: key=1 (60 bytes)[BTREE] Cell 1: key=2 (60 bytes)[BTREE] Cell 2: key=3 (60 bytes)>btree find 3[BTREE] Find key=3: FOUND (page=0 cell=2)[BTREE] Row: id=3 name='carol' email='carol@example.com'>btree find 9[BTREE] Find key=9: NOT_FOUND>insert into users (id, name, email) values (1, 'mallory', 'm@example.com');[ERROR:00701] Key 1 already exists>select * from users;id | name | email1 | alice | alice@example.com2 | bob | bob@example.com3 | carol | carol@example.com(3 rows)>select name, id from users;name | idalice | 1bob | 2carol | 3(3 rows)>.exitexiting.. good bye!
Three things to notice. The rows were inserted as 3, 1, 2 and come back as 1, 2, 3. Key 3 was inserted first and is reported in cell 2, because the two later inserts moved it. And after the rejected insert, row 1 is still alice. The error messages are yours to word; the tests check only the codes.
btree find prints the row from the wrong cell. If FOUND reports cell=2 but the row printed is another one, the search result and the read are using different positions.(0 rows) are still printed.btree find prints the wrong row for a key. A cell is 64 bytes, key and row, and moves as one.btree find reports where the key was written. The cell is the key's position now. Inserting a smaller key moves it.btree dump will show it. Fix the insert, not the output.The page above is the same for every language. These are the only language-specific hints.
memmove is the overlap-safe move; memcpy is not.slice::copy_within moves a range inside one buffer and handles overlap.std.mem.copyBackwards is the one to use when the destination is to the right of the source.