# Postgres-specific type normalizers # Only compiled when Postgrex types are available if Code.ensure_loaded?(Postgrex.Range) do defimpl Lotus.Export.Normalizer, for: Postgrex.Range do def normalize(%Postgrex.Range{} = range) do lower_bound = if range.lower_inclusive, do: "[", else: "(" upper_bound = if range.upper_inclusive, do: "]", else: ")" lower = format_range_value(range.lower) upper = format_range_value(range.upper) "#{lower_bound}#{lower},#{upper}#{upper_bound}" end defp format_range_value(nil), do: "" defp format_range_value(:unbound), do: "" defp format_range_value(value) do Lotus.Export.Normalizer.normalize(value) end end end if Code.ensure_loaded?(Postgrex.INET) do defimpl Lotus.Export.Normalizer, for: Postgrex.INET do def normalize(%Postgrex.INET{address: address, netmask: netmask}) do addr_str = format_address(address) case netmask do nil -> addr_str # IPv4 full mask 32 -> addr_str # IPv6 full mask 128 -> addr_str mask -> "#{addr_str}/#{mask}" end end defp format_address(address) when is_tuple(address) do case tuple_size(address) do # IPv4 4 -> address |> Tuple.to_list() |> Enum.join(".") # IPv6 8 -> address |> Tuple.to_list() |> Enum.map(&Integer.to_string(&1, 16)) |> Enum.join(":") end end end end if Code.ensure_loaded?(Postgrex.Interval) do defimpl Lotus.Export.Normalizer, for: Postgrex.Interval do # Postgrex.Interval has: months, days, secs, microsecs def normalize(%Postgrex.Interval{} = interval) do parts = build_date_parts(interval) time_parts = build_time_parts(interval) format_duration(parts, time_parts) end defp build_date_parts(interval) do [] |> add_years_months(interval.months || 0) |> add_days(interval.days) end defp add_years_months(parts, 0), do: parts defp add_years_months(parts, total_months) do years = div(total_months, 12) months = rem(total_months, 12) parts |> add_if_nonzero(years, "Y") |> add_if_nonzero(months, "M") end defp add_days(parts, nil), do: parts defp add_days(parts, 0), do: parts defp add_days(parts, days), do: parts ++ ["#{days}D"] defp add_if_nonzero(parts, 0, _suffix), do: parts defp add_if_nonzero(parts, value, suffix), do: parts ++ ["#{value}#{suffix}"] defp build_time_parts(interval) do total_microsecs = (interval.secs || 0) * 1_000_000 + (interval.microsecs || 0) format_time_parts(total_microsecs) end defp format_duration([], []), do: "P0D" defp format_duration(parts, []), do: "P" <> Enum.join(parts) defp format_duration(parts, time_parts) do "P" <> Enum.join(parts ++ ["T"] ++ time_parts) end defp format_time_parts(0), do: [] defp format_time_parts(microsecs) do total_secs = div(microsecs, 1_000_000) remaining_microsecs = rem(microsecs, 1_000_000) hours = div(total_secs, 3600) minutes = div(rem(total_secs, 3600), 60) seconds = rem(total_secs, 60) parts = [] parts = if seconds != 0 or remaining_microsecs != 0 do if remaining_microsecs != 0 do # Format with fractional seconds frac_seconds = seconds + remaining_microsecs / 1_000_000 parts ++ ["#{Float.to_string(frac_seconds)}S"] else parts ++ ["#{seconds}S"] end else parts end parts = if minutes != 0, do: ["#{minutes}M"] ++ parts, else: parts parts = if hours != 0, do: ["#{hours}H"] ++ parts, else: parts parts end end end if Code.ensure_loaded?(Postgrex.Point) do defimpl Lotus.Export.Normalizer, for: Postgrex.Point do def normalize(%Postgrex.Point{x: x, y: y}) do "(#{x},#{y})" end end end if Code.ensure_loaded?(Postgrex.LineSegment) do defimpl Lotus.Export.Normalizer, for: Postgrex.LineSegment do def normalize(%Postgrex.LineSegment{point1: p1, point2: p2}) do "[#{Lotus.Export.Normalizer.normalize(p1)},#{Lotus.Export.Normalizer.normalize(p2)}]" end end end if Code.ensure_loaded?(Postgrex.Box) do defimpl Lotus.Export.Normalizer, for: Postgrex.Box do def normalize(%Postgrex.Box{upper_right: ur, bottom_left: bl}) do "(#{Lotus.Export.Normalizer.normalize(ur)},#{Lotus.Export.Normalizer.normalize(bl)})" end end end if Code.ensure_loaded?(Postgrex.Path) do defimpl Lotus.Export.Normalizer, for: Postgrex.Path do def normalize(%Postgrex.Path{points: points, open: open}) do points_str = points |> Enum.map(&Lotus.Export.Normalizer.normalize/1) |> Enum.join(",") if open do "[#{points_str}]" else "(#{points_str})" end end end end if Code.ensure_loaded?(Postgrex.Polygon) do defimpl Lotus.Export.Normalizer, for: Postgrex.Polygon do def normalize(%Postgrex.Polygon{vertices: vertices}) do points_str = vertices |> Enum.map(&Lotus.Export.Normalizer.normalize/1) |> Enum.join(",") "(#{points_str})" end end end if Code.ensure_loaded?(Postgrex.Circle) do defimpl Lotus.Export.Normalizer, for: Postgrex.Circle do def normalize(%Postgrex.Circle{center: center, radius: radius}) do "<#{Lotus.Export.Normalizer.normalize(center)},#{radius}>" end end end