# frozen_string_literal: true

# https://cstack.github.io/db_tutorial/parts/part3.html
module Sqliterb
  class Table
    # In-Memory page
    class Page
      PAGE_SIZE = 4096

      def initialize
        bytes = Array.new(PAGE_SIZE) { 0 }.pack('C*')
        @memory = StringIO.new(bytes).binmode
      end

      def read(pos, serializer)
        @memory.pos = pos
        serializer.read(@memory)
      end

      def write(pos, serializer)
        @memory.pos = pos
        serializer.write(@memory)
      end
    end

    class Serializer < BinData::Record
      SIZE = 32 + 32 + 255

      endian :little

      uint32 :id
      string :username, length: 32, trim_padding: true
      string :email,    length: 255, trim_padding: true
    end

    MAX_PAGES = 100
    ROW_SIZE = Serializer::SIZE
    ROWS_PER_PAGE = Page::PAGE_SIZE / ROW_SIZE
    TABLE_MAX_ROWS = ROWS_PER_PAGE * MAX_PAGES

    def initialize
      @pages = Array.new(MAX_PAGES)
      @rows_num = 0
    end

    def read(_row_data)
      @rows_num.times do |row_num|
        page, offset = find_row_slot(row_num)

        record = Serializer.new
        page.read(offset, record)

        print_row(record)
      end
    end

    def write(row_data)
      raise 'EXECUTE_TABLE_FULL' if @rows_num >= TABLE_MAX_ROWS

      page, offset = find_row_slot(@rows_num)

      page.write(offset, Serializer.new(normalize_args(row_data)))
      @rows_num += 1
    end

    private

    def print_row(data)
      puts "(#{data.snapshot.values.join(', ')})"
    end

    # TODO: брать атрибуты из сериалайзера
    def normalize_args(data)
      {
        id:       data[0].to_i,
        username: data[1],
        email:    data[2]
      }
    end

    def find_row_slot(row_num)
      page_num = row_num / ROWS_PER_PAGE
      page =
        if @pages[page_num].nil?
          @pages[page_num] = Page.new
        else
          @pages[page_num]
        end

      row_offset = row_num % ROWS_PER_PAGE
      byte_offset = row_offset * ROW_SIZE

      [page, byte_offset]
    end
  end
end

Изменить пасту