-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathBTreeFile.java
More file actions
1256 lines (1151 loc) · 48.9 KB
/
Copy pathBTreeFile.java
File metadata and controls
1256 lines (1151 loc) · 48.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package simpledb;
import java.io.*;
import java.util.*;
import simpledb.Predicate.Op;
/**
* BTreeFile is an implementation of a DbFile that stores a B+ tree.
* Specifically, it stores a pointer to a root page,
* a set of internal pages, and a set of leaf pages, which contain a collection of tuples
* in sorted order. BTreeFile works closely with BTreeLeafPage, BTreeInternalPage,
* and BTreeRootPtrPage. The format of these pages is described in their constructors.
*
* @see simpledb.BTreeLeafPage#BTreeLeafPage
* @see simpledb.BTreeInternalPage#BTreeInternalPage
* @see simpledb.BTreeHeaderPage#BTreeHeaderPage
* @see simpledb.BTreeRootPtrPage#BTreeRootPtrPage
* @author Becca Taft
*/
public class BTreeFile implements DbFile {
private final File f;
private final TupleDesc td;
private final int tableid ;
private int keyField;
/**
* Constructs a B+ tree file backed by the specified file.
*
* @param f - the file that stores the on-disk backing store for this B+ tree
* file.
* @param key - the field which index is keyed on
* @param td - the tuple descriptor of tuples in the file
*/
public BTreeFile(File f, int key, TupleDesc td) {
this.f = f;
this.tableid = f.getAbsoluteFile().hashCode();
this.keyField = key;
this.td = td;
}
/**
* Returns the File backing this BTreeFile on disk.
*/
public File getFile() {
return f;
}
/**
* Returns an ID uniquely identifying this BTreeFile. Implementation note:
* you will need to generate this tableid somewhere and ensure that each
* BTreeFile has a "unique id," and that you always return the same value for
* a particular BTreeFile. We suggest hashing the absolute file name of the
* file underlying the BTreeFile, i.e. f.getAbsoluteFile().hashCode().
*
* @return an ID uniquely identifying this BTreeFile.
*/
public int getId() {
return tableid;
}
/**
* Returns the TupleDesc of the table stored in this DbFile.
*
* @return TupleDesc of this DbFile.
*/
public TupleDesc getTupleDesc() {
return td;
}
/**
* Read a page from the file on disk. This should not be called directly
* but should be called from the BufferPool via getPage()
*
* @param pid - the id of the page to read from disk
* @return the page constructed from the contents on disk
*/
public Page readPage(PageId pid) {
BTreePageId id = (BTreePageId) pid;
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(f));
if(id.pgcateg() == BTreePageId.ROOT_PTR) {
byte pageBuf[] = new byte[BTreeRootPtrPage.getPageSize()];
int retval = bis.read(pageBuf, 0, BTreeRootPtrPage.getPageSize());
if (retval == -1) {
throw new IllegalArgumentException("Read past end of table");
}
if (retval < BTreeRootPtrPage.getPageSize()) {
throw new IllegalArgumentException("Unable to read "
+ BTreeRootPtrPage.getPageSize() + " bytes from BTreeFile");
}
Debug.log(1, "BTreeFile.readPage: read page %d", id.getPageNumber());
BTreeRootPtrPage p = new BTreeRootPtrPage(id, pageBuf);
return p;
}
else {
byte pageBuf[] = new byte[BufferPool.getPageSize()];
if (bis.skip(BTreeRootPtrPage.getPageSize() + (id.getPageNumber()-1) * BufferPool.getPageSize()) !=
BTreeRootPtrPage.getPageSize() + (id.getPageNumber()-1) * BufferPool.getPageSize()) {
throw new IllegalArgumentException(
"Unable to seek to correct place in BTreeFile");
}
int retval = bis.read(pageBuf, 0, BufferPool.getPageSize());
if (retval == -1) {
throw new IllegalArgumentException("Read past end of table");
}
if (retval < BufferPool.getPageSize()) {
throw new IllegalArgumentException("Unable to read "
+ BufferPool.getPageSize() + " bytes from BTreeFile");
}
Debug.log(1, "BTreeFile.readPage: read page %d", id.getPageNumber());
if(id.pgcateg() == BTreePageId.INTERNAL) {
BTreeInternalPage p = new BTreeInternalPage(id, pageBuf, keyField);
return p;
}
else if(id.pgcateg() == BTreePageId.LEAF) {
BTreeLeafPage p = new BTreeLeafPage(id, pageBuf, keyField);
return p;
}
else { // id.pgcateg() == BTreePageId.HEADER
BTreeHeaderPage p = new BTreeHeaderPage(id, pageBuf);
return p;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// Close the file on success or error
try {
if (bis != null)
bis.close();
} catch (IOException ioe) {
// Ignore failures closing the file
}
}
}
/**
* Write a page to disk. This should not be called directly but should
* be called from the BufferPool when pages are flushed to disk
*
* @param page - the page to write to disk
*/
public void writePage(Page page) throws IOException {
BTreePageId id = (BTreePageId) page.getId();
byte[] data = page.getPageData();
RandomAccessFile rf = new RandomAccessFile(f, "rw");
if(id.pgcateg() == BTreePageId.ROOT_PTR) {
rf.write(data);
rf.close();
}
else {
rf.seek(BTreeRootPtrPage.getPageSize() + (page.getId().getPageNumber()-1) * BufferPool.getPageSize());
rf.write(data);
rf.close();
}
}
/**
* Returns the number of pages in this BTreeFile.
*/
public int numPages() {
// we only ever write full pages
return (int) ((f.length() - BTreeRootPtrPage.getPageSize())/ BufferPool.getPageSize());
}
/**
* Returns the index of the field that this B+ tree is keyed on
*/
public int keyField() {
return keyField;
}
/**
* Recursive function which finds and locks the leaf page in the B+ tree corresponding to
* the left-most page possibly containing the key field f. It locks all internal
* nodes along the path to the leaf node with READ_ONLY permission, and locks the
* leaf node with permission perm.
*
* If f is null, it finds the left-most leaf page -- used for the iterator
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param pid - the current page being searched
* @param perm - the permissions with which to lock the leaf page
* @param f - the field to search for
* @return the left-most leaf page possibly containing the key field f
*
*/
private BTreeLeafPage findLeafPage(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreePageId pid, Permissions perm,
Field f)
throws DbException, TransactionAbortedException {
// some code goes here
return null;
}
/**
* Convenience method to find a leaf page when there is no dirtypages HashMap.
* Used by the BTreeFile iterator.
* @see #findLeafPage(TransactionId, HashMap, BTreePageId, Permissions, Field)
*
* @param tid - the transaction id
* @param pid - the current page being searched
* @param perm - the permissions with which to lock the leaf page
* @param f - the field to search for
* @return the left-most leaf page possibly containing the key field f
*
*/
BTreeLeafPage findLeafPage(TransactionId tid, BTreePageId pid, Permissions perm,
Field f)
throws DbException, TransactionAbortedException {
return findLeafPage(tid, new HashMap<PageId, Page>(), pid, perm, f);
}
/**
* Split a leaf page to make room for new tuples and recursively split the parent node
* as needed to accommodate a new entry. The new entry should have a key matching the key field
* of the first tuple in the right-hand page (the key is "copied up"), and child pointers
* pointing to the two leaf pages resulting from the split. Update sibling pointers and parent
* pointers as needed.
*
* Return the leaf page into which a new tuple with key field "field" should be inserted.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the leaf page to split
* @param field - the key field of the tuple to be inserted after the split is complete. Necessary to know
* which of the two pages to return.
* @see #getParentWithEmptySlots(TransactionId, HashMap, BTreePageId, Field)
*
* @return the leaf page into which the new tuple should be inserted
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected BTreeLeafPage splitLeafPage(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreeLeafPage page, Field field)
throws DbException, IOException, TransactionAbortedException {
// some code goes here
//
// Split the leaf page by adding a new page on the right of the existing
// page and moving half of the tuples to the new page. Copy the middle key up
// into the parent page, and recursively split the parent as needed to accommodate
// the new entry. getParentWithEmtpySlots() will be useful here. Don't forget to update
// the sibling pointers of all the affected leaf pages. Return the page into which a
// tuple with the given key field should be inserted.
return null;
}
/**
* Split an internal page to make room for new entries and recursively split its parent page
* as needed to accommodate a new entry. The new entry for the parent should have a key matching
* the middle key in the original internal page being split (this key is "pushed up" to the parent).
* The child pointers of the new parent entry should point to the two internal pages resulting
* from the split. Update parent pointers as needed.
*
* Return the internal page into which an entry with key field "field" should be inserted
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the internal page to split
* @param field - the key field of the entry to be inserted after the split is complete. Necessary to know
* which of the two pages to return.
* @see #getParentWithEmptySlots(TransactionId, HashMap, BTreePageId, Field)
* @see #updateParentPointers(TransactionId, HashMap, BTreeInternalPage)
*
* @return the internal page into which the new entry should be inserted
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected BTreeInternalPage splitInternalPage(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeInternalPage page, Field field)
throws DbException, IOException, TransactionAbortedException {
// some code goes here
//
// Split the internal page by adding a new page on the right of the existing
// page and moving half of the entries to the new page. Push the middle key up
// into the parent page, and recursively split the parent as needed to accommodate
// the new entry. getParentWithEmtpySlots() will be useful here. Don't forget to update
// the parent pointers of all the children moving to the new page. updateParentPointers()
// will be useful here. Return the page into which an entry with the given key field
// should be inserted.
return null;
}
/**
* Method to encapsulate the process of getting a parent page ready to accept new entries.
* This may mean creating a page to become the new root of the tree, splitting the existing
* parent page if there are no empty slots, or simply locking and returning the existing parent page.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param parentId - the id of the parent. May be an internal page or the RootPtr page
* @param field - the key of the entry which will be inserted. Needed in case the parent must be split
* to accommodate the new entry
* @return the parent page, guaranteed to have at least one empty slot
* @see #splitInternalPage(TransactionId, HashMap, BTreeInternalPage, Field)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private BTreeInternalPage getParentWithEmptySlots(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreePageId parentId, Field field) throws DbException, IOException, TransactionAbortedException {
BTreeInternalPage parent = null;
// create a parent node if necessary
// this will be the new root of the tree
if(parentId.pgcateg() == BTreePageId.ROOT_PTR) {
parent = (BTreeInternalPage) getEmptyPage(tid, dirtypages, BTreePageId.INTERNAL);
// update the root pointer
BTreeRootPtrPage rootPtr = (BTreeRootPtrPage) getPage(tid, dirtypages,
BTreeRootPtrPage.getId(tableid), Permissions.READ_WRITE);
BTreePageId prevRootId = rootPtr.getRootId(); //save prev id before overwriting.
rootPtr.setRootId(parent.getId());
// update the previous root to now point to this new root.
BTreePage prevRootPage = (BTreePage)getPage(tid, dirtypages, prevRootId, Permissions.READ_WRITE);
prevRootPage.setParentId(parent.getId());
}
else {
// lock the parent page
parent = (BTreeInternalPage) getPage(tid, dirtypages, parentId,
Permissions.READ_WRITE);
}
// split the parent if needed
if(parent.getNumEmptySlots() == 0) {
parent = splitInternalPage(tid, dirtypages, parent, field);
}
return parent;
}
/**
* Helper function to update the parent pointer of a node.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param pid - id of the parent node
* @param child - id of the child node to be updated with the parent pointer
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void updateParentPointer(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreePageId pid, BTreePageId child)
throws DbException, IOException, TransactionAbortedException {
BTreePage p = (BTreePage) getPage(tid, dirtypages, child, Permissions.READ_ONLY);
if(!p.getParentId().equals(pid)) {
p = (BTreePage) getPage(tid, dirtypages, child, Permissions.READ_WRITE);
p.setParentId(pid);
}
}
/**
* Update the parent pointer of every child of the given page so that it correctly points to
* the parent
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the parent page
* @see #updateParentPointer(TransactionId, HashMap, BTreePageId, BTreePageId)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void updateParentPointers(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreeInternalPage page)
throws DbException, IOException, TransactionAbortedException{
Iterator<BTreeEntry> it = page.iterator();
BTreePageId pid = page.getId();
BTreeEntry e = null;
while(it.hasNext()) {
e = it.next();
updateParentPointer(tid, dirtypages, pid, e.getLeftChild());
}
if(e != null) {
updateParentPointer(tid, dirtypages, pid, e.getRightChild());
}
}
/**
* Method to encapsulate the process of locking/fetching a page. First the method checks the local
* cache ("dirtypages"), and if it can't find the requested page there, it fetches it from the buffer pool.
* It also adds pages to the dirtypages cache if they are fetched with read-write permission, since
* presumably they will soon be dirtied by this transaction.
*
* This method is needed to ensure that page updates are not lost if the same pages are
* accessed multiple times.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param pid - the id of the requested page
* @param perm - the requested permissions on the page
* @return the requested page
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
Page getPage(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreePageId pid, Permissions perm)
throws DbException, TransactionAbortedException {
if(dirtypages.containsKey(pid)) {
return dirtypages.get(pid);
}
else {
Page p = Database.getBufferPool().getPage(tid, pid, perm);
if(perm == Permissions.READ_WRITE) {
dirtypages.put(pid, p);
}
return p;
}
}
/**
* Insert a tuple into this BTreeFile, keeping the tuples in sorted order.
* May cause pages to split if the page where tuple t belongs is full.
*
* @param tid - the transaction id
* @param t - the tuple to insert
* @return a list of all pages that were dirtied by this operation. Could include
* many pages since parent pointers will need to be updated when an internal node splits.
* @see #splitLeafPage(TransactionId, HashMap, BTreeLeafPage, Field)
*/
public ArrayList<Page> insertTuple(TransactionId tid, Tuple t)
throws DbException, IOException, TransactionAbortedException {
HashMap<PageId, Page> dirtypages = new HashMap<PageId, Page>();
// get a read lock on the root pointer page and use it to locate the root page
BTreeRootPtrPage rootPtr = getRootPtrPage(tid, dirtypages);
BTreePageId rootId = rootPtr.getRootId();
if(rootId == null) { // the root has just been created, so set the root pointer to point to it
rootId = new BTreePageId(tableid, numPages(), BTreePageId.LEAF);
rootPtr = (BTreeRootPtrPage) getPage(tid, dirtypages, BTreeRootPtrPage.getId(tableid), Permissions.READ_WRITE);
rootPtr.setRootId(rootId);
}
// find and lock the left-most leaf page corresponding to the key field,
// and split the leaf page if there are no more slots available
BTreeLeafPage leafPage = findLeafPage(tid, dirtypages, rootId, Permissions.READ_WRITE, t.getField(keyField));
if(leafPage.getNumEmptySlots() == 0) {
leafPage = splitLeafPage(tid, dirtypages, leafPage, t.getField(keyField));
}
// insert the tuple into the leaf page
leafPage.insertTuple(t);
ArrayList<Page> dirtyPagesArr = new ArrayList<Page>();
dirtyPagesArr.addAll(dirtypages.values());
return dirtyPagesArr;
}
/**
* Handle the case when a B+ tree page becomes less than half full due to deletions.
* If one of its siblings has extra tuples/entries, redistribute those tuples/entries.
* Otherwise merge with one of the siblings. Update pointers as needed.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the page which is less than half full
* @see #handleMinOccupancyLeafPage(TransactionId, HashMap, BTreeLeafPage, BTreeInternalPage, BTreeEntry, BTreeEntry)
* @see #handleMinOccupancyInternalPage(TransactionId, HashMap, BTreeInternalPage, BTreeInternalPage, BTreeEntry, BTreeEntry)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void handleMinOccupancyPage(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreePage page)
throws DbException, IOException, TransactionAbortedException {
BTreePageId parentId = page.getParentId();
BTreeEntry leftEntry = null;
BTreeEntry rightEntry = null;
BTreeInternalPage parent = null;
// find the left and right siblings through the parent so we make sure they have
// the same parent as the page. Find the entries in the parent corresponding to
// the page and siblings
if(parentId.pgcateg() != BTreePageId.ROOT_PTR) {
parent = (BTreeInternalPage) getPage(tid, dirtypages, parentId, Permissions.READ_WRITE);
Iterator<BTreeEntry> ite = parent.iterator();
while(ite.hasNext()) {
BTreeEntry e = ite.next();
if(e.getLeftChild().equals(page.getId())) {
rightEntry = e;
break;
}
else if(e.getRightChild().equals(page.getId())) {
leftEntry = e;
}
}
}
if(page.getId().pgcateg() == BTreePageId.LEAF) {
handleMinOccupancyLeafPage(tid, dirtypages, (BTreeLeafPage) page, parent, leftEntry, rightEntry);
}
else { // BTreePageId.INTERNAL
handleMinOccupancyInternalPage(tid, dirtypages, (BTreeInternalPage) page, parent, leftEntry, rightEntry);
}
}
/**
* Handle the case when a leaf page becomes less than half full due to deletions.
* If one of its siblings has extra tuples, redistribute those tuples.
* Otherwise merge with one of the siblings. Update pointers as needed.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the leaf page which is less than half full
* @param parent - the parent of the leaf page
* @param leftEntry - the entry in the parent pointing to the given page and its left-sibling
* @param rightEntry - the entry in the parent pointing to the given page and its right-sibling
* @see #mergeLeafPages(TransactionId, HashMap, BTreeLeafPage, BTreeLeafPage, BTreeInternalPage, BTreeEntry)
* @see #stealFromLeafPage(BTreeLeafPage, BTreeLeafPage, BTreeInternalPage, BTreeEntry, boolean)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void handleMinOccupancyLeafPage(TransactionId tid, HashMap<PageId, Page> dirtypages, BTreeLeafPage page,
BTreeInternalPage parent, BTreeEntry leftEntry, BTreeEntry rightEntry)
throws DbException, IOException, TransactionAbortedException {
BTreePageId leftSiblingId = null;
BTreePageId rightSiblingId = null;
if(leftEntry != null) leftSiblingId = leftEntry.getLeftChild();
if(rightEntry != null) rightSiblingId = rightEntry.getRightChild();
int maxEmptySlots = page.getMaxTuples() - page.getMaxTuples()/2; // ceiling
if(leftSiblingId != null) {
BTreeLeafPage leftSibling = (BTreeLeafPage) getPage(tid, dirtypages, leftSiblingId, Permissions.READ_WRITE);
// if the left sibling is at minimum occupancy, merge with it. Otherwise
// steal some tuples from it
if(leftSibling.getNumEmptySlots() >= maxEmptySlots) {
mergeLeafPages(tid, dirtypages, leftSibling, page, parent, leftEntry);
}
else {
stealFromLeafPage(page, leftSibling, parent, leftEntry, false);
}
}
else if(rightSiblingId != null) {
BTreeLeafPage rightSibling = (BTreeLeafPage) getPage(tid, dirtypages, rightSiblingId, Permissions.READ_WRITE);
// if the right sibling is at minimum occupancy, merge with it. Otherwise
// steal some tuples from it
if(rightSibling.getNumEmptySlots() >= maxEmptySlots) {
mergeLeafPages(tid, dirtypages, page, rightSibling, parent, rightEntry);
}
else {
stealFromLeafPage(page, rightSibling, parent, rightEntry, true);
}
}
}
/**
* Steal tuples from a sibling and copy them to the given page so that both pages are at least
* half full. Update the parent's entry so that the key matches the key field of the first
* tuple in the right-hand page.
*
* @param page - the leaf page which is less than half full
* @param sibling - the sibling which has tuples to spare
* @param parent - the parent of the two leaf pages
* @param entry - the entry in the parent pointing to the two leaf pages
* @param isRightSibling - whether the sibling is a right-sibling
*
* @throws DbException
*/
protected void stealFromLeafPage(BTreeLeafPage page, BTreeLeafPage sibling,
BTreeInternalPage parent, BTreeEntry entry, boolean isRightSibling) throws DbException {
// some code goes here
//
// Move some of the tuples from the sibling to the page so
// that the tuples are evenly distributed. Be sure to update
// the corresponding parent entry.
}
/**
* Handle the case when an internal page becomes less than half full due to deletions.
* If one of its siblings has extra entries, redistribute those entries.
* Otherwise merge with one of the siblings. Update pointers as needed.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the internal page which is less than half full
* @param parent - the parent of the internal page
* @param leftEntry - the entry in the parent pointing to the given page and its left-sibling
* @param rightEntry - the entry in the parent pointing to the given page and its right-sibling
* @see #mergeInternalPages(TransactionId, HashMap, BTreeInternalPage, BTreeInternalPage, BTreeInternalPage, BTreeEntry)
* @see #stealFromLeftInternalPage(TransactionId, HashMap, BTreeInternalPage, BTreeInternalPage, BTreeInternalPage, BTreeEntry)
* @see #stealFromRightInternalPage(TransactionId, HashMap, BTreeInternalPage, BTreeInternalPage, BTreeInternalPage, BTreeEntry)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void handleMinOccupancyInternalPage(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeInternalPage page, BTreeInternalPage parent, BTreeEntry leftEntry, BTreeEntry rightEntry)
throws DbException, IOException, TransactionAbortedException {
BTreePageId leftSiblingId = null;
BTreePageId rightSiblingId = null;
if(leftEntry != null) leftSiblingId = leftEntry.getLeftChild();
if(rightEntry != null) rightSiblingId = rightEntry.getRightChild();
int maxEmptySlots = page.getMaxEntries() - page.getMaxEntries()/2; // ceiling
if(leftSiblingId != null) {
BTreeInternalPage leftSibling = (BTreeInternalPage) getPage(tid, dirtypages, leftSiblingId, Permissions.READ_WRITE);
// if the left sibling is at minimum occupancy, merge with it. Otherwise
// steal some entries from it
if(leftSibling.getNumEmptySlots() >= maxEmptySlots) {
mergeInternalPages(tid, dirtypages, leftSibling, page, parent, leftEntry);
}
else {
stealFromLeftInternalPage(tid, dirtypages, page, leftSibling, parent, leftEntry);
}
}
else if(rightSiblingId != null) {
BTreeInternalPage rightSibling = (BTreeInternalPage) getPage(tid, dirtypages, rightSiblingId, Permissions.READ_WRITE);
// if the right sibling is at minimum occupancy, merge with it. Otherwise
// steal some entries from it
if(rightSibling.getNumEmptySlots() >= maxEmptySlots) {
mergeInternalPages(tid, dirtypages, page, rightSibling, parent, rightEntry);
}
else {
stealFromRightInternalPage(tid, dirtypages, page, rightSibling, parent, rightEntry);
}
}
}
/**
* Steal entries from the left sibling and copy them to the given page so that both pages are at least
* half full. Keys can be thought of as rotating through the parent entry, so the original key in the
* parent is "pulled down" to the right-hand page, and the last key in the left-hand page is "pushed up"
* to the parent. Update parent pointers as needed.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the internal page which is less than half full
* @param leftSibling - the left sibling which has entries to spare
* @param parent - the parent of the two internal pages
* @param parentEntry - the entry in the parent pointing to the two internal pages
* @see #updateParentPointers(TransactionId, HashMap, BTreeInternalPage)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected void stealFromLeftInternalPage(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeInternalPage page, BTreeInternalPage leftSibling, BTreeInternalPage parent,
BTreeEntry parentEntry) throws DbException, IOException, TransactionAbortedException {
// some code goes here
// Move some of the entries from the left sibling to the page so
// that the entries are evenly distributed. Be sure to update
// the corresponding parent entry. Be sure to update the parent
// pointers of all children in the entries that were moved.
}
/**
* Steal entries from the right sibling and copy them to the given page so that both pages are at least
* half full. Keys can be thought of as rotating through the parent entry, so the original key in the
* parent is "pulled down" to the left-hand page, and the last key in the right-hand page is "pushed up"
* to the parent. Update parent pointers as needed.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param page - the internal page which is less than half full
* @param rightSibling - the right sibling which has entries to spare
* @param parent - the parent of the two internal pages
* @param parentEntry - the entry in the parent pointing to the two internal pages
* @see #updateParentPointers(TransactionId, HashMap, BTreeInternalPage)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected void stealFromRightInternalPage(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeInternalPage page, BTreeInternalPage rightSibling, BTreeInternalPage parent,
BTreeEntry parentEntry) throws DbException, IOException, TransactionAbortedException {
// some code goes here
// Move some of the entries from the right sibling to the page so
// that the entries are evenly distributed. Be sure to update
// the corresponding parent entry. Be sure to update the parent
// pointers of all children in the entries that were moved.
}
/**
* Merge two leaf pages by moving all tuples from the right page to the left page.
* Delete the corresponding key and right child pointer from the parent, and recursively
* handle the case when the parent gets below minimum occupancy.
* Update sibling pointers as needed, and make the right page available for reuse.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param leftPage - the left leaf page
* @param rightPage - the right leaf page
* @param parent - the parent of the two pages
* @param parentEntry - the entry in the parent corresponding to the leftPage and rightPage
* @see #deleteParentEntry(TransactionId, HashMap, BTreePage, BTreeInternalPage, BTreeEntry)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected void mergeLeafPages(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeLeafPage leftPage, BTreeLeafPage rightPage, BTreeInternalPage parent, BTreeEntry parentEntry)
throws DbException, IOException, TransactionAbortedException {
// some code goes here
//
// Move all the tuples from the right page to the left page, update
// the sibling pointers, and make the right page available for reuse.
// Delete the entry in the parent corresponding to the two pages that are merging -
// deleteParentEntry() will be useful here
}
/**
* Merge two internal pages by moving all entries from the right page to the left page
* and "pulling down" the corresponding key from the parent entry.
* Delete the corresponding key and right child pointer from the parent, and recursively
* handle the case when the parent gets below minimum occupancy.
* Update parent pointers as needed, and make the right page available for reuse.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param leftPage - the left internal page
* @param rightPage - the right internal page
* @param parent - the parent of the two pages
* @param parentEntry - the entry in the parent corresponding to the leftPage and rightPage
* @see #deleteParentEntry(TransactionId, HashMap, BTreePage, BTreeInternalPage, BTreeEntry)
* @see #updateParentPointers(TransactionId, HashMap, BTreeInternalPage)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected void mergeInternalPages(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreeInternalPage leftPage, BTreeInternalPage rightPage, BTreeInternalPage parent, BTreeEntry parentEntry)
throws DbException, IOException, TransactionAbortedException {
// some code goes here
//
// Move all the entries from the right page to the left page, update
// the parent pointers of the children in the entries that were moved,
// and make the right page available for reuse
// Delete the entry in the parent corresponding to the two pages that are merging -
// deleteParentEntry() will be useful here
}
/**
* Method to encapsulate the process of deleting an entry (specifically the key and right child)
* from a parent node. If the parent becomes empty (no keys remaining), that indicates that it
* was the root node and should be replaced by its one remaining child. Otherwise, if it gets
* below minimum occupancy for non-root internal nodes, it should steal from one of its siblings or
* merge with a sibling.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param leftPage - the child remaining after the key and right child are deleted
* @param parent - the parent containing the entry to be deleted
* @param parentEntry - the entry to be deleted
* @see #handleMinOccupancyPage(TransactionId, HashMap, BTreePage)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private void deleteParentEntry(TransactionId tid, HashMap<PageId, Page> dirtypages,
BTreePage leftPage, BTreeInternalPage parent, BTreeEntry parentEntry)
throws DbException, IOException, TransactionAbortedException {
// delete the entry in the parent. If
// the parent is below minimum occupancy, get some tuples from its siblings
// or merge with one of the siblings
parent.deleteKeyAndRightChild(parentEntry);
int maxEmptySlots = parent.getMaxEntries() - parent.getMaxEntries()/2; // ceiling
if(parent.getNumEmptySlots() == parent.getMaxEntries()) {
// This was the last entry in the parent.
// In this case, the parent (root node) should be deleted, and the merged
// page will become the new root
BTreePageId rootPtrId = parent.getParentId();
if(rootPtrId.pgcateg() != BTreePageId.ROOT_PTR) {
throw new DbException("attempting to delete a non-root node");
}
BTreeRootPtrPage rootPtr = (BTreeRootPtrPage) getPage(tid, dirtypages, rootPtrId, Permissions.READ_WRITE);
leftPage.setParentId(rootPtrId);
rootPtr.setRootId(leftPage.getId());
// release the parent page for reuse
setEmptyPage(tid, dirtypages, parent.getId().getPageNumber());
}
else if(parent.getNumEmptySlots() > maxEmptySlots) {
handleMinOccupancyPage(tid, dirtypages, parent);
}
}
/**
* Delete a tuple from this BTreeFile.
* May cause pages to merge or redistribute entries/tuples if the pages
* become less than half full.
*
* @param tid - the transaction id
* @param t - the tuple to delete
* @return a list of all pages that were dirtied by this operation. Could include
* many pages since parent pointers will need to be updated when an internal node merges.
* @see #handleMinOccupancyPage(TransactionId, HashMap, BTreePage)
*/
public ArrayList<Page> deleteTuple(TransactionId tid, Tuple t)
throws DbException, IOException, TransactionAbortedException {
HashMap<PageId, Page> dirtypages = new HashMap<PageId, Page>();
BTreePageId pageId = new BTreePageId(tableid, t.getRecordId().getPageId().getPageNumber(),
BTreePageId.LEAF);
BTreeLeafPage page = (BTreeLeafPage) getPage(tid, dirtypages, pageId, Permissions.READ_WRITE);
page.deleteTuple(t);
// if the page is below minimum occupancy, get some tuples from its siblings
// or merge with one of the siblings
int maxEmptySlots = page.getMaxTuples() - page.getMaxTuples()/2; // ceiling
if(page.getNumEmptySlots() > maxEmptySlots) {
handleMinOccupancyPage(tid, dirtypages, page);
}
ArrayList<Page> dirtyPagesArr = new ArrayList<Page>();
dirtyPagesArr.addAll(dirtypages.values());
return dirtyPagesArr;
}
/**
* Get a read lock on the root pointer page. Create the root pointer page and root page
* if necessary.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @return the root pointer page
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
BTreeRootPtrPage getRootPtrPage(TransactionId tid, HashMap<PageId, Page> dirtypages) throws DbException, IOException, TransactionAbortedException {
synchronized(this) {
if(f.length() == 0) {
// create the root pointer page and the root page
BufferedOutputStream bw = new BufferedOutputStream(
new FileOutputStream(f, true));
byte[] emptyRootPtrData = BTreeRootPtrPage.createEmptyPageData();
byte[] emptyLeafData = BTreeLeafPage.createEmptyPageData();
bw.write(emptyRootPtrData);
bw.write(emptyLeafData);
bw.close();
}
}
// get a read lock on the root pointer page
return (BTreeRootPtrPage) getPage(tid, dirtypages, BTreeRootPtrPage.getId(tableid), Permissions.READ_ONLY);
}
/**
* Get the page number of the first empty page in this BTreeFile.
* Creates a new page if none of the existing pages are empty.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @return the page number of the first empty page
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected int getEmptyPageNo(TransactionId tid, HashMap<PageId, Page> dirtypages)
throws DbException, IOException, TransactionAbortedException {
// get a read lock on the root pointer page and use it to locate the first header page
BTreeRootPtrPage rootPtr = getRootPtrPage(tid, dirtypages);
BTreePageId headerId = rootPtr.getHeaderId();
int emptyPageNo = 0;
if(headerId != null) {
BTreeHeaderPage headerPage = (BTreeHeaderPage) getPage(tid, dirtypages, headerId, Permissions.READ_ONLY);
int headerPageCount = 0;
// try to find a header page with an empty slot
while(headerPage != null && headerPage.getEmptySlot() == -1) {
headerId = headerPage.getNextPageId();
if(headerId != null) {
headerPage = (BTreeHeaderPage) getPage(tid, dirtypages, headerId, Permissions.READ_ONLY);
headerPageCount++;
}
else {
headerPage = null;
}
}
// if headerPage is not null, it must have an empty slot
if(headerPage != null) {
headerPage = (BTreeHeaderPage) getPage(tid, dirtypages, headerId, Permissions.READ_WRITE);
int emptySlot = headerPage.getEmptySlot();
headerPage.markSlotUsed(emptySlot, true);
emptyPageNo = headerPageCount * BTreeHeaderPage.getNumSlots() + emptySlot;
}
}
// at this point if headerId is null, either there are no header pages
// or there are no free slots
if(headerId == null) {
synchronized(this) {
// create the new page
BufferedOutputStream bw = new BufferedOutputStream(
new FileOutputStream(f, true));
byte[] emptyData = BTreeInternalPage.createEmptyPageData();
bw.write(emptyData);
bw.close();
emptyPageNo = numPages();
}
}
return emptyPageNo;
}
/**
* Method to encapsulate the process of creating a new page. It reuses old pages if possible,
* and creates a new page if none are available. It wipes the page on disk and in the cache and
* returns a clean copy locked with read-write permission
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param pgcateg - the BTreePageId category of the new page. Either LEAF, INTERNAL, or HEADER
* @return the new empty page
* @see #getEmptyPageNo(TransactionId, HashMap)
* @see #setEmptyPage(TransactionId, HashMap, int)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
private Page getEmptyPage(TransactionId tid, HashMap<PageId, Page> dirtypages, int pgcateg)
throws DbException, IOException, TransactionAbortedException {
// create the new page
int emptyPageNo = getEmptyPageNo(tid, dirtypages);
BTreePageId newPageId = new BTreePageId(tableid, emptyPageNo, pgcateg);
// write empty page to disk
RandomAccessFile rf = new RandomAccessFile(f, "rw");
rf.seek(BTreeRootPtrPage.getPageSize() + (emptyPageNo-1) * BufferPool.getPageSize());
rf.write(BTreePage.createEmptyPageData());
rf.close();
// make sure the page is not in the buffer pool or in the local cache
Database.getBufferPool().discardPage(newPageId);
dirtypages.remove(newPageId);
return getPage(tid, dirtypages, newPageId, Permissions.READ_WRITE);
}
/**
* Mark a page in this BTreeFile as empty. Find the corresponding header page
* (create it if needed), and mark the corresponding slot in the header page as empty.
*
* @param tid - the transaction id
* @param dirtypages - the list of dirty pages which should be updated with all new dirty pages
* @param emptyPageNo - the page number of the empty page
* @see #getEmptyPage(TransactionId, HashMap, int)
*
* @throws DbException
* @throws IOException
* @throws TransactionAbortedException
*/
protected void setEmptyPage(TransactionId tid, HashMap<PageId, Page> dirtypages, int emptyPageNo)
throws DbException, IOException, TransactionAbortedException {
// if this is the last page in the file (and not the only page), just
// truncate the file
// @TODO: Commented out because we should probably do this somewhere else in case the transaction aborts....
// synchronized(this) {
// if(emptyPageNo == numPages()) {
// if(emptyPageNo <= 1) {
// // if this is the only page in the file, just return.
// // It just means we have an empty root page
// return;
// }
// long newSize = f.length() - BufferPool.getPageSize();
// FileOutputStream fos = new FileOutputStream(f, true);
// FileChannel fc = fos.getChannel();
// fc.truncate(newSize);
// fc.close();
// fos.close();
// return;
// }
// }
// otherwise, get a read lock on the root pointer page and use it to locate
// the first header page
BTreeRootPtrPage rootPtr = getRootPtrPage(tid, dirtypages);
BTreePageId headerId = rootPtr.getHeaderId();
BTreePageId prevId = null;