Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use MemoryIO. #44

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 68 additions & 126 deletions src/pg/decoder.cr
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
Expand Down