Current section
Files
Jump to
Current section
Files
lib/extensions/range.ex
defmodule :posterize_xt_range do
@moduledoc false
import Postgrex.BinaryUtils, warn: false
use Bitwise, only_operators: true
@behaviour Postgrex.SuperExtension
@empty << 1 >>
def init(_), do: nil
def matching(_), do: [send: "range_send"]
def format(_), do: :super_binary
def oids(%Postgrex.TypeInfo{base_type: base_oid}, _) do
[base_oid]
end
def encode(_) do
quote location: :keep do
:empty, [_oid], [_type] -> :posterize_xt_range.do_encode(:empty)
range, [oid], [type] ->
bounds = Map.get(range, :bounds, :'[)')
up = Map.get(range, :upper, @null)
low = Map.get(range, :lower, @null)
# encode_value/2 defined by TypeModule
upper = encode_value(up, type)
lower = encode_value(low, type)
:posterize_xt_range.do_encode(upper, lower, bounds)
other, _, _ ->
raise ArgumentError, Postgrex.Utils.encode_msg(other, "a map describing a postgres range")
end
end
def do_encode(:empty), do: @empty
def do_encode(<< -1 :: int32 >>, << -1 :: int32 >>, bounds) do
flags = encode_flags(:empty, :empty, bounds)
[ << 1 :: int32 >> | flags ]
end
def do_encode(<< -1 :: int32 >>, lower, bounds) do
flags = encode_flags(:empty, lower, bounds)
[ << (IO.iodata_length(lower) + 1) :: int32 >>, flags | lower ]
end
def do_encode(upper, << -1 :: int32 >>, bounds) do
flags = encode_flags(upper, :empty, bounds)
[ << (IO.iodata_length(upper) + 1) :: int32 >>, flags | upper ]
end
def do_encode(upper, lower, bounds) do
flags = encode_flags(upper, lower, bounds)
[ << (IO.iodata_length([ lower | upper ]) + 1) :: int32 >>, flags | [ lower | upper ] ]
end
defp encode_flags(:empty, :empty, _bounds) do
<< 0 :: 3, 1 :: 1, 1 :: 1, 0 :: 1, 0 :: 1, 0 :: 1 >>
end
defp encode_flags(:empty, _lower, bounds) do
{ lower_inc, upper_inc } = encode_bounds(bounds)
<< 0 :: 3, 1 :: 1, 0 :: 1, upper_inc :: 1, lower_inc :: 1, 0 :: 1 >>
end
defp encode_flags(_upper, :empty, bounds) do
{ lower_inc, upper_inc } = encode_bounds(bounds)
<< 0 :: 3, 0 :: 1, 1 :: 1, upper_inc :: 1, lower_inc :: 1, 0 :: 1 >>
end
defp encode_flags(_upper, _lower, bounds) do
{ lower_inc, upper_inc } = encode_bounds(bounds)
<< 0 :: 3, 0 :: 1, 0 :: 1, upper_inc :: 1, lower_inc :: 1, 0 :: 1 >>
end
defp encode_bounds(:'[]'), do: { 1, 1 }
defp encode_bounds(:'[)'), do: { 1, 0 }
defp encode_bounds(:'(]'), do: { 0, 1 }
defp encode_bounds(:'()'), do: { 0, 0 }
def decode(_) do
quote location: :keep do
<< length :: int32, binary :: binary-size(length) >>, [oid], [type] ->
<< flags :: binary-size(1), data :: binary >> = binary
# decode_list/2 defined by TypeModule
elements = decode_list(data, type)
:posterize_xt_range.do_decode(flags, elements)
end
end
def do_decode(flags, elements) do
case empty?(flags) do
true -> :empty
false -> %{} |> upper(elements, flags) |> lower(elements, flags) |> bounds(flags)
end
end
defp upper(range, elements, flags) do
case upper_infinity?(flags) do
true -> range
false -> Map.put(range, :upper, hd(elements))
end
end
defp lower(range, elements, flags) do
case lower_infinity?(flags) do
true -> range
false -> Map.put(range, :lower, hd(Enum.reverse(elements)))
end
end
defp bounds(range, flags) do
bounds = case { lower_inclusive?(flags), upper_inclusive?(flags) } do
{ true, true } -> :'[]'
{ true, false } -> :'[)'
{ false, true } -> :'(]'
{ false, false } -> :'()'
end
Map.put(range, :bounds, bounds)
end
defp upper_infinity?(<< _ :: 3, 1 :: 1, _ :: 4 >>), do: true
defp upper_infinity?(_), do: false
defp lower_infinity?(<< _ :: 4, 1 :: 1, _ :: 3 >>), do: true
defp lower_infinity?(_), do: false
defp upper_inclusive?(<< _ :: 5, 1 :: 1, _ :: 2 >>), do: true
defp upper_inclusive?(_), do: false
defp lower_inclusive?(<< _ :: 6, 1 :: 1, _ :: 1 >>), do: true
defp lower_inclusive?(_), do: false
defp empty?(<< _ :: 7, 1 :: 1 >>), do: true
defp empty?(_), do: false
end