# encoding: US-ASCII require 'ostruct' require 'binary_struct' require_relative 'MiqSqlite3Util' module MiqSqlite3DB class MiqSqlite3Cell attr_accessor :left_child, :key, :data, :pointer def initialize(page, pointer) @page = page @pointer = pointer @data = @key = @left_child = @fields = nil offset = nData = 0 unless @page.leaf @left_child = @page.buf[@pointer + offset, 4].unpack('N')[0] offset += 4 end if @page.hasData nData, count = MiqSqlite3DB.variableInteger(@page.buf[@pointer + offset, 9]) offset += count end nKey, count = MiqSqlite3DB.variableInteger(@page.buf[@pointer + offset, 9]) offset += count if @page.intKey @key = nKey nKey = 0 end getPayload(nData, nKey, offset) end def fields return @fields if @fields return nil if @data.nil? len = @data[0].ord @fields = [] offset = len byte = 1 while byte < len val, count = MiqSqlite3DB.variableInteger(@data[byte..-1]) byte += count flen, type = decodeField(val) field = {} if flen == 0 if type == 'null' field['type'] = 'text' field['data'] = nil else field['type'] = 'boolean' field['data'] = true if type == 'true' field['data'] = false if type == 'false' end else field['len'] = flen field['type'] = type fdata = @data[offset, flen] offset += flen if type == 'integer' fdata = "\x00".concat(fdata) if flen == 3 field['data'] = fdata.unpack("C")[0] if flen == 1 field['data'] = fdata.unpack("n")[0] if flen == 2 field['data'] = fdata.unpack("N")[0] if flen == 3 || flen == 4 else field['data'] = fdata end end @fields << field end @fields end def dump puts "======== Dumping Cell =========" puts "Page: #{@page.pagenum}" puts "Cell: #{@pointer}" puts "Left Child: #{@left_child}" if @left_child puts "Key: #{@key}" if @key puts "Data: #{@data}" if @data puts "Data Length: #{@data.size}" if @data fields.each do |f| p f end end private def getPayload(nData, nKey, offset) nPayload = nData + nKey if nPayload <= @page.maxLocal ## This is the (easy) common case where the entire payload fits ## on the local page. No overflow is required. nLocal = nPayload iOverflow = 0 nSize = nPayload + offset # Total size of cell content in bytes nSize = 4 if nSize < 4 # Minimum cell size is 4 @data = @page.buf[@pointer + offset, nData] if nData > 0 @key = @page.buf[@pointer + offset + nData, nKey] if nKey > 0 else ## If the payload will not fit completely on the local page, we have ## to decide how much to store locally and how much to spill onto ## overflow pages. The strategy is to minimize the amount of unused ## space on overflow pages while keeping the amount of local storage ## in between minLocal and maxLocal. ## ## Warning: changing the way overflow payload is distributed in any ## way will result in an incompatible file format. ## minLocal = @page.minLocal # Minimum amount of payload held locally maxLocal = @page.maxLocal # Maximum amount of payload held locally surplus = minLocal + (nPayload - minLocal) % (@db.usableSize - 4) # Overflow payload available for local storage nLocal = (surplus <= maxLocal) ? surplus : minLocal iOverflow = nLocal + offset nSize = iOverflow + 4 @overflow = @page.buf[@pointer + iOverflow, 4].unpack('N')[0] raise "cell (#{@number}): PAYLOAD OVERFLOW NOT SUPPORTED" end end # ** The following table describes the various storage classes for data: # ** # ** serial type bytes of data type # ** -------------- --------------- --------------- # ** 0 0 NULL # ** 1 1 signed integer # ** 2 2 signed integer # ** 3 3 signed integer # ** 4 4 signed integer # ** 5 6 signed integer # ** 6 8 signed integer # ** 7 8 IEEE float # ** 8 0 Integer constant 0 # ** 9 0 Integer constant 1 # ** 10,11 reserved for expansion # ** N>=12 and even (N-12)/2 BLOB # ** N>=13 and odd (N-13)/2 text def decodeField(serial_type) case serial_type when 0 then return 0, 'null' when 1 then return 1, 'integer' when 2 then return 2, 'integer' when 3 then return 3, 'integer' when 4 then return 4, 'integer' when 5 then return 6, 'integer' when 6 then return 8, 'integer' when 7 then return 8, 'float' when 8 then return 0, 'false' when 9 then return 0, 'true' when 10 then raise "Unknown Column Type #{serial_type}" when 11 then raise "Unknown Column Type #{serial_type}" end type = serial_type.even? ? 'blob' : 'text' len = (serial_type - 12) / 2 return len, type end end end