static void TestDBRecord() { // init record List <AtomValue> values = new List <AtomValue>(); AtomValue value1 = new AtomValue() { Type = AttributeTypes.Int, IntegerValue = 222 }; AtomValue value2 = new AtomValue() { Type = AttributeTypes.Null }; AtomValue value3 = new AtomValue() { Type = AttributeTypes.Char, CharLimit = 5, StringValue = "222" }; values.Add(value1); values.Add(value2); values.Add(value3); DBRecord record = new DBRecord(values); // make raw bytes List <byte> rawNode = new List <byte>(); rawNode.AddRange(new byte[30]); // clone List <AtomValue> valuesOut1 = record.GetValues(); // clone byte[] raw = record.Pack(); rawNode.AddRange(raw); record.Unpack(rawNode.ToArray(), 30); List <AtomValue> valuesOut2 = record.GetValues(); int i; for (i = 0; i < valuesOut2.Count; i++) { AssertAtomValue(values[i], valuesOut2[i]); AssertAtomValue(valuesOut1[i], valuesOut2[i]); } }
static void AssertDBRecords(DBRecord r1, DBRecord r2) { List <AtomValue> valuesOut1 = r1.GetValues(); List <AtomValue> valuesOut2 = r2.GetValues(); Assert.Equal(valuesOut1.Count, valuesOut2.Count); int i; for (i = 0; i < valuesOut2.Count; i++) { AssertAtomValue(valuesOut1[i], valuesOut2[i]); } }
/// <summary> /// Split `nodeTobeSplit`, which has a `parantNode`. /// </summary> /// <param name="nodeTobeSplit">node to be split.</param> /// <param name="parantNode">parent node of `nodeTobeSplit`.</param> /// <param name="newKey">the value of new key. This could affect where to split.</param> private void SplitNode(BTreeNode nodeTobeSplit, BTreeNode parantNode, DBRecord newKey) { int i; // new node will be appended to `nodeTobeSplit` BTreeNode splitNode = GetNewNode(nodeTobeSplit.PageType); // tmp is used for the change of parentPage InternalTableCell tmpCell; MemoryPage tmpPage; // key should be the primary key to be inserted in the parent node int deleteIndex = nodeTobeSplit.NumCells / 2; // right bound of the left node DBRecord rightmostKeyInLeftNode = nodeTobeSplit[deleteIndex - 1].Key; if ((newKey.GetValues()[0] >= rightmostKeyInLeftNode.GetValues()[0]).BooleanValue) { // the new key should be put at the split node on the right deleteIndex++; rightmostKeyInLeftNode = nodeTobeSplit[deleteIndex - 1].Key; } // `deleteIndex` is now pointing to the first cell of the pending right node // loop through the right part of `nodeTobeSplit` for (i = deleteIndex; i < this.MaxCell; i++) { ushort deleteOffSet = nodeTobeSplit.CellOffsetArray[deleteIndex]; // change the parent Page fields of their children if (nodeTobeSplit.PageType == PageTypes.InternalTablePage) { tmpCell = (InternalTableCell)nodeTobeSplit.GetBTreeCell(deleteOffSet); tmpPage = _pager.ReadPage((int)tmpCell.ChildPage); new BTreeNode(tmpPage) { ParentPage = (uint)splitNode.RawPage.PageNumber }; } // move the cells in `nodeTobeSplit` belonging to the pending right node to `splitNode` splitNode.InsertBTreeCell(nodeTobeSplit.GetBTreeCell(deleteOffSet)); nodeTobeSplit.DeleteBTreeCell(deleteOffSet); } // new cell to be inserted to the parent node of `nodeTobeSplit` // If the `parentNode` need to spilt, this will be wrong. However, it could be ensured not happenning because it is a top-down process. InternalTableCell newCellToAscend = new InternalTableCell(rightmostKeyInLeftNode, (uint)nodeTobeSplit.RawPage.PageNumber); // make alias for readability BTreeNode leftNode = nodeTobeSplit; BTreeNode rightNode = splitNode; // connect two nodes `leftNode` and `rightNode` by `RightPage` pointer rightNode.RightPage = leftNode.RightPage; if (leftNode.PageType == PageTypes.LeafTablePage) { leftNode.RightPage = (uint)rightNode.RawPage.PageNumber; } // if `leftNode` is an internal node, the middle cell in the original `nodeTobeSplit` (the rightmost cell in the now `leftNode`) has to be deleted else if (leftNode.PageType == PageTypes.InternalTablePage) { // for internal node, the `ParentPage` of child page need to be changed // the rightmost child of `leftNode` sets its parent to `rightNode` tmpPage = _pager.ReadPage((int)leftNode.RightPage); new BTreeNode(tmpPage) { ParentPage = (uint)rightNode.RawPage.PageNumber }; // the rightmost Cell In the pending Left Node InternalTableCell rightmostCellInLeftNode = (InternalTableCell)leftNode[deleteIndex - 1]; // set the rightmost child of `leftNode` to the left child of `rightmostCellInLeftNode` leftNode.RightPage = rightmostCellInLeftNode.ChildPage; // leftNode.DeleteBTreeCell(rightmostCellInLeftNode); // the same as the code below, but the code below runs faster leftNode.DeleteBTreeCell(leftNode.CellOffsetArray[deleteIndex - 1]); // the deleted rightmost cell in left node sets the parent of its left child to that left node tmpPage = _pager.ReadPage((int)rightmostCellInLeftNode.ChildPage); new BTreeNode(tmpPage) { ParentPage = (uint)leftNode.RawPage.PageNumber }; } rightNode.ParentPage = (uint)parantNode.RawPage.PageNumber; // reconnect the particular cell (or right) in the parent node because of the change of the child node // This is a new empty root if (parantNode.NumCells == 0) { parantNode.RightPage = (uint)rightNode.RawPage.PageNumber; } // There are some cell in the parents node else { BTreeCell cell; (cell, _, _) = parantNode.FindBTreeCell(rightmostKeyInLeftNode); // The case when the node to be inserted into parent node is in the rightest position if (cell == null) { parantNode.RightPage = (uint)rightNode.RawPage.PageNumber; } // The normal case else { InternalTableCell tmp_cell = new InternalTableCell(cell.Key, (uint)rightNode.RawPage.PageNumber); parantNode.DeleteBTreeCell(cell); parantNode.InsertBTreeCell(tmp_cell); } } // This must be done after we reconnect the treeNode parantNode.InsertBTreeCell(newCellToAscend); }
/// <summary> /// <para>Insert cell into a non-full node `node` if `node` is a leaf node.</para> /// <para>If `node` is not the leaf node, the child node of `node` might need to split before recursively performing this function to the child node.</para> /// <para>`node` should be ensured to be not full.</para> /// </summary> /// <param name="node">node being inserted</param> /// <param name="newKey">new key value</param> /// <param name="dBRecord">new row/record</param> /// <returns>new root node</returns> private BTreeNode InsertNonFull(BTreeNode node, DBRecord newKey, DBRecord dBRecord) { // the actual insertion performs in leaf node if (node.PageType == PageTypes.LeafTablePage) { BTreeCell check_repeat; (check_repeat, _, _) = node.FindBTreeCell(newKey, false); if (check_repeat != null) { throw new RepeatedKeyException($"The primary key to be inserted ({newKey.GetValues()[0]}) is repeated!"); } LeafTableCell newCell = new LeafTableCell(newKey, dBRecord); node.InsertBTreeCell(newCell); return(node); } // find the child node to try to insert BTreeNode child; BTreeCell cell; UInt16 offset; (cell, offset, _) = node.FindBTreeCell(newKey); MemoryPage nextpage; if (offset == 0) // rightest page { nextpage = _pager.ReadPage((int)node.RightPage); child = new BTreeNode(nextpage); } else { InternalTableCell internalTableCell = (InternalTableCell)cell; nextpage = _pager.ReadPage((int)internalTableCell.ChildPage); child = new BTreeNode(nextpage); } // child needs to split if (child.NumCells >= MaxCell) { SplitNode(child, node, newKey); return(InsertNonFull(node, newKey, dBRecord)); } return(InsertNonFull(child, newKey, dBRecord)); }