class Innodb::DataType::DecimalType

MySQL’s Fixed-Point Type (DECIMAL), stored in InnoDB as a binary string.

Constants

BYTES_PER_DIGIT
MAX_DIGITS_PER_INTEGER

The value is stored as a sequence of signed big-endian integers, each representing up to 9 digits of the integral and fractional parts. The first integer of the integral part and/or the last integer of the fractional part might be compressed (or packed) and are of variable length. The remaining integers (if any) are uncompressed and 32 bits wide.

Attributes

name[R]
width[R]

Public Class Methods

new(base_type, modifiers, properties) click to toggle source
# File lib/innodb/data_type.rb, line 108
def initialize(base_type, modifiers, properties)
  precision, scale = sanity_check(modifiers)
  integral = precision - scale
  @uncomp_integral = integral / MAX_DIGITS_PER_INTEGER
  @uncomp_fractional = scale / MAX_DIGITS_PER_INTEGER
  @comp_integral = integral - (@uncomp_integral * MAX_DIGITS_PER_INTEGER)
  @comp_fractional = scale - (@uncomp_fractional * MAX_DIGITS_PER_INTEGER)
  @width = (@uncomp_integral * 4) + BYTES_PER_DIGIT[@comp_integral] +
           (@comp_fractional * 4) + BYTES_PER_DIGIT[@comp_fractional]
  @name = Innodb::DataType.make_name(base_type, modifiers, properties)
end

Public Instance Methods

value(data) click to toggle source
# File lib/innodb/data_type.rb, line 120
def value(data)
  # Strings representing the integral and fractional parts.
  intg = "".dup
  frac = "".dup

  stream = StringIO.new(data)
  mask = sign_mask(stream)

  intg << get_digits(stream, mask, @comp_integral)

  (1..@uncomp_integral).each do
    intg << get_digits(stream, mask, MAX_DIGITS_PER_INTEGER)
  end

  (1..@uncomp_fractional).each do
    frac << get_digits(stream, mask, MAX_DIGITS_PER_INTEGER)
  end

  frac << get_digits(stream, mask, @comp_fractional)
  frac = "0" if frac.empty?

  # Convert to something resembling a string representation.
  str = "#{mask.to_s.chop}#{intg}.#{frac}"

  BigDecimal(str).to_s("F")
end

Private Instance Methods

get_digits(stream, mask, digits) click to toggle source

Return a string representing an integer with a specific number of digits.

# File lib/innodb/data_type.rb, line 176
def get_digits(stream, mask, digits)
  nbits = BYTES_PER_DIGIT[digits] * 8
  return "" unless nbits.positive?

  value = (BinData.const_get("Int%dbe" % nbits).read(stream) ^ mask)
  # Preserve leading zeros.
  "%0#{digits}d" % value
end
sanity_check(modifiers) click to toggle source

Ensure width specification (if any) is compliant.

# File lib/innodb/data_type.rb, line 150
def sanity_check(modifiers)
  raise "Invalid width specification" unless modifiers.size <= 2

  precision = modifiers.fetch(0, 10)
  raise "Unsupported precision for DECIMAL type" unless precision >= 1 && precision <= 65

  scale = modifiers.fetch(1, 0)
  raise "Unsupported scale for DECIMAL type" unless scale >= 0 && scale <= 30 && scale <= precision

  [precision, scale]
end
sign_mask(stream) click to toggle source

The sign is encoded in the high bit of the first byte/digit. The byte might be part of a larger integer, so apply the bit-flipper and push back the byte into the stream.

# File lib/innodb/data_type.rb, line 165
def sign_mask(stream)
  byte = BinData::Uint8.read(stream)
  sign = byte & 0x80
  byte.assign(byte ^ 0x80)
  stream.rewind
  byte.write(stream)
  stream.rewind
  sign.zero? ? -1 : 0
end