diff --git a/src/pg/decoder.cr b/src/pg/decoder.cr index 5ce3c193..b7247e6e 100644 --- a/src/pg/decoder.cr +++ b/src/pg/decoder.cr @@ -1,219 +1,165 @@ require "json" +require "./numeric" module PG + alias PGValue = String | Nil | Bool | Int32 | Float32 | Float64 | Time | JSON::Type | PG::Numeric # :nodoc: module Decoders - abstract class Decoder - abstract def decode(bytes) - - private def swap16(slice : Slice(UInt8)) - swap16(slice.pointer(0)) - end - private def swap16(ptr : UInt8*) : UInt16 - ((((0_u16 - ) | ptr[0]) << 8 - ) | ptr[1]) - end + # When subclassing overwrite #decode(io : IO) or #decode(bytes : Slice(UInt8)). Decoder is used via + # #decode(bytes : Slice(UInt8)) only. + class Decoder - private def swap32(slice : Slice(UInt8)) - swap32(slice.pointer(0)) + def decode(io : IO) + raise "Not supported, please use #decode(bytes : Slice(UInt8))." end - private def swap32(ptr : UInt8*) : UInt32 - ((((((((0_u32 - ) | ptr[0]) << 8 - ) | ptr[1]) << 8 - ) | ptr[2]) << 8 - ) | ptr[3]) + def decode(bytes : Slice(UInt8)) + decode MemoryIO.new bytes end - private def swap64(slice : Slice(UInt8)) - swap64(slice.pointer(0)) - end + {% for type in %w(Int8 UInt8 Int16 UInt16 Int32 UInt32 Int64 UInt64 Float32 Float64) %} + def decode(type : {{type.id}}.class, io : IO) + IO::ByteFormat::NetworkEndian.decode {{type.id}}, io + end + {% end %} - private def swap64(ptr : UInt8*) : UInt64 - ((((((((((((((((0_u64 - ) | ptr[0]) << 8 - ) | ptr[1]) << 8 - ) | ptr[2]) << 8 - ) | ptr[3]) << 8 - ) | ptr[4]) << 8 - ) | ptr[5]) << 8 - ) | ptr[6]) << 8 - ) | ptr[7]) - end end class StringDecoder < Decoder - def decode(bytes) - String.new(bytes) + def decode(bytes : Slice(UInt8)) + String.new bytes end end class CharDecoder < Decoder - def decode(bytes) + def decode(bytes : Slice(UInt8)) String.new(bytes)[0] end end class BoolDecoder < Decoder - def decode(bytes) - case bytes[0] + def decode(io : IO) + case value = decode UInt8, io when 0 false when 1 true else - raise "bad boolean decode: #{bytes[0]}" + raise "Invalid bool, expected 0 or 1, but got #{value}." end end end class Int2Decoder < Decoder - def decode(bytes) - swap16(bytes).to_i16 + def decode(io : IO) + decode Int16, io end end class IntDecoder < Decoder - def decode(bytes) - swap32(bytes).to_i32 + def decode(io : IO) + decode Int32, io end end class UIntDecoder < Decoder - def decode(bytes) - swap32(bytes).to_u32 + def decode(io : IO) + decode UInt32, io end end class Int8Decoder < Decoder - def decode(bytes) - swap64(bytes).to_i64 + def decode(io : IO) + decode Int64, io end end class Float32Decoder < Decoder - # byte swapped in the same way as int4 - def decode(bytes) - u32 = swap32(bytes) - (pointerof(u32).as(Float32*)).value + def decode(io : IO) + decode Float32, io end end class Float64Decoder < Decoder - def decode(bytes) - u64 = swap64(bytes) - (pointerof(u64).as(Float64*)).value + def decode(io : IO) + decode Float64, io end end class PointDecoder < Decoder - def decode(bytes) - x = swap64(bytes) - y = swap64(bytes + 8) - - { - (pointerof(x).as(Float64*)).value, - (pointerof(y).as(Float64*)).value, - } + def decode(io : IO) + {decode(Float64, io), decode(Float64, io)} end end class PathDecoder < Decoder - def initialize - @polygon = PolygonDecoder.new - end - - def decode(bytes) - status = (bytes[0] == 1_u8 ? :closed : :open) - {status, @polygon.decode(bytes + 1)} + def decode(io : IO) + status = (decode(UInt8, io) == 1_u8 ? :closed : :open) + polygon = PolygonDecoder.new.decode io + {status, polygon} end end class PolygonDecoder < Decoder - def decode(bytes) - c = swap32(bytes) - count = (pointerof(c).as(Int32*)).value - - Array(Tuple(Float64, Float64)).new(count) do |i| - offset = i*16 + 4 - x = swap64(bytes + offset) - y = swap64(bytes + (offset + 8)) - - { - (pointerof(x).as(Float64*)).value, - (pointerof(y).as(Float64*)).value, - } + def decode(io : IO) + point_decoder = PointDecoder.new + count = decode Int32, io + Array(Tuple(Float64, Float64)).new(count) do + point_decoder.decode io end end end class BoxDecoder < Decoder - def decode(bytes) - x1 = swap64(bytes) - y1 = swap64(bytes + 8) - x2 = swap64(bytes + 16) - y2 = swap64(bytes + 24) - - { { - (pointerof(x1).as(Float64*)).value, - (pointerof(y1).as(Float64*)).value, - }, { - (pointerof(x2).as(Float64*)).value, - (pointerof(y2).as(Float64*)).value, - } } + def decode(io : IO) + point_decoder = PointDecoder.new + {point_decoder.decode(io), point_decoder.decode(io)} end end class LineDecoder < Decoder - def decode(bytes) - a = swap64(bytes) - b = swap64(bytes + 8) - c = swap64(bytes + 16) - - { - (pointerof(a).as(Float64*)).value, - (pointerof(b).as(Float64*)).value, - (pointerof(c).as(Float64*)).value, - } + def decode(io : IO) + {decode(Float64, io), decode(Float64, io), decode(Float64, io)} end end class JsonDecoder < Decoder - def decode(bytes) - JSON.parse(String.new(bytes)) + def decode(bytes : Slice(UInt8)) + JSON.parse String.new(bytes) end end class JsonbDecoder < Decoder - def decode(bytes) - # move past single 0x01 byte at the start of jsonb - JSON.parse(String.new(bytes + 1)) + def decode(bytes : Slice(UInt8)) + if bytes[0] == 0x01 + JSON.parse String.new(bytes + 1) + else + raise "Invalid jsonb, expected 0x01 byte." + end end end JAN_1_2K_TICKS = Time.new(2000, 1, 1, kind: Time::Kind::Utc).ticks class DateDecoder < Decoder - def decode(bytes) - v = swap32(bytes).to_i32 + def decode(io : IO) + v = decode Int32, io Time.new(JAN_1_2K_TICKS + (Time::Span::TicksPerDay * v), kind: Time::Kind::Utc) end end class TimeDecoder < Decoder - def decode(bytes) - v = swap64(bytes).to_i64 / 1000 + def decode(io : IO) + v = decode(Int64, io) / 1000 Time.new(JAN_1_2K_TICKS + (Time::Span::TicksPerMillisecond * v), kind: Time::Kind::Utc) end end class UuidDecoder < Decoder - def decode(bytes) + def decode(bytes : Slice(UInt8)) String.new(36) do |buffer| buffer[8] = buffer[13] = buffer[18] = buffer[23] = 45_u8 bytes[0, 4].hexstring(buffer + 0) @@ -227,24 +173,20 @@ module PG end class ByteaDecoder < Decoder - def decode(bytes) + def decode(bytes : Slice(UInt8)) bytes end end class NumericDecoder < Decoder - def decode(bytes) - ndigits = i16 bytes[0, 2] - weight = i16 bytes[2, 2] - sign = i16 bytes[4, 2] - dscale = i16 bytes[6, 2] - digits = (0...ndigits).map { |i| i16 bytes[i*2 + 8, 2] } + def decode(io : IO) + ndigits = decode Int16, io + weight = decode Int16, io + sign = decode Int16, io + dscale = decode Int16, io + digits = Array(Int16).new(ndigits.to_i32) { decode Int16, io } PG::Numeric.new(ndigits, weight, sign, dscale, digits) end - - private def i16(bytes) - swap16(bytes).to_i16 - end end @@decoders = Hash(Int32, PG::Decoders::Decoder).new(ByteaDecoder.new)