From 9265511cf2fac45d8145cf82555dbacb164a1975 Mon Sep 17 00:00:00 2001 From: Mirek Rusin Date: Sat, 28 May 2016 09:41:51 -0300 Subject: [PATCH 1/2] Use MemoryIO. --- src/pg/decoder.cr | 208 ++++++++++++++++------------------------------ 1 file changed, 73 insertions(+), 135 deletions(-) diff --git a/src/pg/decoder.cr b/src/pg/decoder.cr index 5ce3c193..d417a035 100644 --- a/src/pg/decoder.cr +++ b/src/pg/decoder.cr @@ -1,250 +1,188 @@ 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) - String.new(36) do |buffer| - buffer[8] = buffer[13] = buffer[18] = buffer[23] = 45_u8 - bytes[0, 4].hexstring(buffer + 0) - bytes[4, 2].hexstring(buffer + 9) - bytes[6, 2].hexstring(buffer + 14) - bytes[8, 2].hexstring(buffer + 19) - bytes[10, 6].hexstring(buffer + 24) - {36, 36} - end + def decode(io : IO) + [4, 2, 2, 2, 6].map do |n| + part = Slice(UInt8).new n + io.read_fully part + part.hexstring + end.join '-' end 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) From a43fa05e4c0b8a882f40b06b94356b477a478b54 Mon Sep 17 00:00:00 2001 From: Mirek Rusin Date: Sat, 28 May 2016 18:57:46 -0300 Subject: [PATCH 2/2] Revert UUID parsing to original (it's faster). --- src/pg/decoder.cr | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pg/decoder.cr b/src/pg/decoder.cr index d417a035..b7247e6e 100644 --- a/src/pg/decoder.cr +++ b/src/pg/decoder.cr @@ -159,12 +159,16 @@ module PG end class UuidDecoder < Decoder - def decode(io : IO) - [4, 2, 2, 2, 6].map do |n| - part = Slice(UInt8).new n - io.read_fully part - part.hexstring - end.join '-' + 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) + bytes[4, 2].hexstring(buffer + 9) + bytes[6, 2].hexstring(buffer + 14) + bytes[8, 2].hexstring(buffer + 19) + bytes[10, 6].hexstring(buffer + 24) + {36, 36} + end end end