Currently we have a byteArray to which we append data directly but in an actual database we would have B-trees to help with that we will create a cursor abstaraction that will help us find the page and byte_offset and advance i case of insert

from Pager import *
class Cursor:
    def __init__(self,table,row_num,end_of_table):
        self.table=table
        self.row_num=row_num
        self.end_of_table=end_of_table

    def value(self):
        page_num=self.row_num//ROWS_PER_PAGE
        page=self.table.pager.get_page(page_num)
        
        row_offset = self.row_num % ROWS_PER_PAGE
        byte_offset = row_offset * ROW_SIZE

        return page , byte_offset

    def advance(self):
        self.row_num+=1
        if self.row_num >= self.table.num_rows:
            self.end_of_table=True

The value func is straight forward it returns the page and byte_offset like the original table row_slot function.

The advance function increments the row_num, we will run this after every insert

There fore we now need to make some more changes to our table to remove redundancy

class Table:
    def __init__(self,pager,num_rows):
        self.pager=pager
        self.num_rows=num_rows

    def table_start(self):
        is_end = (self.num_rows ==0) 
        return Cursor(table=self,row_num=0,end_of_table=is_end)
    def table_end(self):
        return Cursor(table=self,row_num=self.num_rows,end_of_table=True)
        

Now instead of the row_slot function we have the table start and table end function these set the cursor to the beginning or the end of the table respectively, one reason for this is that our cursor will now take care of maintaining the position of the elements, but it will make more sense once we update the execute funcs


def execute_insert(statement, table):
    if table.num_rows >= TABLE_MAX_ROWS:
        return "TABLE_FULL"
    cursor=table.table_end()
    page, offset = cursor.value()
    serialize_row(statement["data"], page, offset)
    table.num_rows += 1
    return "SUCCESS"

For the insert command we first find the table end find the page and offset append the data to the byte_array(remember a bytearray is a page) and add a row to the total rows, then finally return.

def execute_select(table):
    cursor= table.table_start()
    while not cursor.end_of_table:
        page, offset = cursor.value()
        row = deserialize_row(page, offset)
        print(f"({row[0]}, {row[1]}, {row[2]})")
        cursor.advance()
 
    return "SUCCESS"

For the select statement we want to start from the beginning and read every line, for this we go to the table start and iterate over each item until cursor hits the end of the table