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

Fix selecting complex polygons/ #225

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d98ac91
add test and fix for complex polygons
pierot Jun 13, 2018
4a4702a
no formatting
pierot Jun 14, 2018
1597d71
merge
pierot Sep 25, 2018
d5b5f8c
Merge branch 'xerions-master'
pierot Sep 25, 2018
c8a4ec9
fix travis, based on https://github.com/xerions/mariaex/pull/236/comm…
pierot Oct 2, 2018
8441a6e
fix test by skipping geometry tests when nu geometry support
pierot Oct 2, 2018
140837c
Merge remote-tracking branch 'upstream/master'
pierot Jan 28, 2019
f6faec3
add multipolygon
jeroenbourgois Sep 13, 2019
6969129
bump
jeroenbourgois Sep 13, 2019
6a81027
tweak
jeroenbourgois Sep 13, 2019
c98d932
another fix attempts
jeroenbourgois Sep 14, 2019
b5b5833
test
jeroenbourgois Sep 17, 2019
e982539
fix
jeroenbourgois Sep 17, 2019
d7bebc9
fix
jeroenbourgois Sep 19, 2019
ec1a857
merge
jeroenbourgois Sep 19, 2019
98ab01d
fix
jeroenbourgois Sep 19, 2019
8c63fcd
test
jeroenbourgois Sep 19, 2019
e5028f7
yaf
jeroenbourgois Sep 19, 2019
6dd9429
logging
jeroenbourgois Sep 19, 2019
5556368
another test
jeroenbourgois Sep 19, 2019
dca804f
another test
jeroenbourgois Sep 19, 2019
a34be28
fix
jeroenbourgois Sep 19, 2019
e1916d1
tmp multipolygon fix
jeroenbourgois Oct 4, 2019
76e3458
fix
jeroenbourgois Oct 4, 2019
46a1088
debug data
jeroenbourgois Oct 4, 2019
4afa78a
mp1
jeroenbourgois Oct 4, 2019
703ba7e
fix
jeroenbourgois Oct 4, 2019
c7a48e6
yat
jeroenbourgois Oct 7, 2019
e652110
fix acc reduce
jeroenbourgois Oct 7, 2019
9d219a6
proress
jeroenbourgois Oct 7, 2019
e58a59d
more
jeroenbourgois Oct 7, 2019
8e8f32a
trying
jeroenbourgois Oct 7, 2019
9fec1c1
fix: FIX
jeroenbourgois Nov 5, 2019
fdd8fad
fix: order matters
jeroenbourgois Nov 5, 2019
811cc18
fix
jeroenbourgois Nov 5, 2019
fa42c77
chore: cleanup
jeroenbourgois Nov 5, 2019
07aa4e5
error
jeroenbourgois Nov 5, 2019
0d4cb27
try bump
jeroenbourgois Nov 5, 2019
f438518
more error output
jeroenbourgois Nov 5, 2019
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
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GEOMETRY_SUPPORT="true"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ erl_crash.dump
.compile.elixir
*.swp
/doc
.elixir_ls
11 changes: 11 additions & 0 deletions lib/mariaex/geometry/multipolygon.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Mariaex.Geometry.MultiPolygon do
@moduledoc """
Define the MultiPolygon struct
"""

@type t :: %Mariaex.Geometry.MultiPolygon{
coordinates: [[{number, number}]],
srid: non_neg_integer | nil
}
defstruct coordinates: [], srid: nil
end
259 changes: 204 additions & 55 deletions lib/mariaex/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,132 +51,272 @@ defimpl DBConnection.Query, for: Mariaex.Query do
This function is called to encode a query before it is executed.
"""
def encode(%Mariaex.Query{type: nil} = query, _params, _opts) do
raise ArgumentError, "query #{inspect query} has not been prepared"
raise ArgumentError, "query #{inspect(query)} has not been prepared"
end

def encode(%Mariaex.Query{num_params: num_params} = query, params, _opts)
when length(params) != num_params do
raise ArgumentError, "parameters must be of length #{num_params} for query #{inspect query}"
raise ArgumentError, "parameters must be of length #{num_params} for query #{inspect(query)}"
end

def encode(%Mariaex.Query{type: :binary, binary_as: binary_as}, params, _opts) do
parameters_to_binary(params, binary_as)
end

def encode(%Mariaex.Query{type: :text}, [], _opts) do
[]
end

defp parameters_to_binary([], _binary_as), do: <<>>

defp parameters_to_binary(params, binary_as) do
set = {0, 0, <<>>, <<>>}
{nullint, len, typesbin, valuesbin} = Enum.reduce(params, set, fn(p, acc) -> encode_params(p, acc, binary_as) end)

{nullint, len, typesbin, valuesbin} =
Enum.reduce(params, set, fn p, acc -> encode_params(p, acc, binary_as) end)

nullbin_size = div(len + 7, 8)
<< nullint :: size(nullbin_size)-little-unit(8), 1 :: 8, typesbin :: binary, valuesbin :: binary >>
<<nullint::size(nullbin_size)-little-unit(8), 1::8, typesbin::binary, valuesbin::binary>>
end

defp encode_params(param, {nullint, idx, typesbin, valuesbin}, binary_as) do
{nullvalue, type, value} = encode_param(param, binary_as)

types_part = case type do
:field_type_longlong ->
# Set the unsigned byte if value > 2^63 (bigint's max signed value).
if param > 9_223_372_036_854_775_807 do
<< typesbin :: binary, 0x8008 :: 16-little >>
else
<< typesbin :: binary, 0x08 :: 16-little >>
end
_ ->
<< typesbin :: binary, Messages.__type__(:id, type) :: 16-little >>
end
types_part =
case type do
:field_type_longlong ->
# Set the unsigned byte if value > 2^63 (bigint's max signed value).
if param > 9_223_372_036_854_775_807 do
<<typesbin::binary, 0x8008::16-little>>
else
<<typesbin::binary, 0x08::16-little>>
end

_ ->
<<typesbin::binary, Messages.__type__(:id, type)::16-little>>
end

{
nullint ||| (nullvalue <<< idx),
nullint ||| nullvalue <<< idx,
idx + 1,
types_part,
<< valuesbin :: binary, value :: binary >>
<<valuesbin::binary, value::binary>>
}
end

defp encode_param(nil, _binary_as),
do: {1, :field_type_null, ""}

defp encode_param(bin, binary_as) when is_binary(bin),
do: {0, binary_as, << to_length_encoded_integer(byte_size(bin)) :: binary, bin :: binary >>}
do: {0, binary_as, <<to_length_encoded_integer(byte_size(bin))::binary, bin::binary>>}

defp encode_param(int, _binary_as) when is_integer(int),
do: {0, :field_type_longlong, << int :: 64-little >>}
do: {0, :field_type_longlong, <<int::64-little>>}

defp encode_param(float, _binary_as) when is_float(float),
do: {0, :field_type_double, << float :: 64-little-float >>}
do: {0, :field_type_double, <<float::64-little-float>>}

defp encode_param(true, _binary_as),
do: {0, :field_type_tiny, << 01 >>}
do: {0, :field_type_tiny, <<01>>}

defp encode_param(false, _binary_as),
do: {0, :field_type_tiny, << 00 >>}
do: {0, :field_type_tiny, <<00>>}

defp encode_param(%Decimal{} = value, _binary_as) do
bin = Decimal.to_string(value, :normal)
{0, :field_type_newdecimal, << to_length_encoded_integer(byte_size(bin)) :: binary, bin :: binary >>}

{0, :field_type_newdecimal,
<<to_length_encoded_integer(byte_size(bin))::binary, bin::binary>>}
end

defp encode_param(%Date{year: year, month: month, day: day}, _binary_as),
do: {0, :field_type_date, << 4::8-little, year::16-little, month::8-little, day::8-little>>}
do: {0, :field_type_date, <<4::8-little, year::16-little, month::8-little, day::8-little>>}

defp encode_param(%Time{hour: hour, minute: min, second: sec, microsecond: {0, 0}}, _binary_as),
do: {0, :field_type_time, << 8 :: 8-little, 0 :: 8-little, 0 :: 32-little, hour :: 8-little, min :: 8-little, sec :: 8-little >>}
defp encode_param(%Time{hour: hour, minute: min, second: sec, microsecond: {msec, _}}, _binary_as),
do: {0, :field_type_time, << 12 :: 8-little, 0 :: 8-little, 0 :: 32-little, hour :: 8-little, min :: 8-little, sec :: 8-little, msec :: 32-little>>}
defp encode_param(%NaiveDateTime{year: year, month: month, day: day,
hour: hour, minute: min, second: sec, microsecond: {0, 0}}, _binary_as),
do: {0, :field_type_datetime, << 7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little>>}
defp encode_param(%NaiveDateTime{year: year, month: month, day: day,
hour: hour, minute: min, second: sec, microsecond: {msec, _}}, _binary_as),
do: {0, :field_type_datetime, <<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little, msec::32-little>>}
defp encode_param(%DateTime{time_zone: "Etc/UTC", year: year, month: month, day: day,
hour: hour, minute: min, second: sec, microsecond: {0, 0}}, _binary_as),
do: {0, :field_type_timestamp, << 7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little>>}
defp encode_param(%DateTime{time_zone: "Etc/UTC", year: year, month: month, day: day,
hour: hour, minute: min, second: sec, microsecond: {msec, _}}, _binary_as),
do: {0, :field_type_timestamp, <<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little, msec::32-little>>}
do:
{0, :field_type_time,
<<8::8-little, 0::8-little, 0::32-little, hour::8-little, min::8-little, sec::8-little>>}

defp encode_param(
%Time{hour: hour, minute: min, second: sec, microsecond: {msec, _}},
_binary_as
),
do:
{0, :field_type_time,
<<12::8-little, 0::8-little, 0::32-little, hour::8-little, min::8-little, sec::8-little,
msec::32-little>>}

defp encode_param(
%NaiveDateTime{
year: year,
month: month,
day: day,
hour: hour,
minute: min,
second: sec,
microsecond: {0, 0}
},
_binary_as
),
do:
{0, :field_type_datetime,
<<7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little>>}

defp encode_param(
%NaiveDateTime{
year: year,
month: month,
day: day,
hour: hour,
minute: min,
second: sec,
microsecond: {msec, _}
},
_binary_as
),
do:
{0, :field_type_datetime,
<<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little, msec::32-little>>}

defp encode_param(
%DateTime{
time_zone: "Etc/UTC",
year: year,
month: month,
day: day,
hour: hour,
minute: min,
second: sec,
microsecond: {0, 0}
},
_binary_as
),
do:
{0, :field_type_timestamp,
<<7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little>>}

defp encode_param(
%DateTime{
time_zone: "Etc/UTC",
year: year,
month: month,
day: day,
hour: hour,
minute: min,
second: sec,
microsecond: {msec, _}
},
_binary_as
),
do:
{0, :field_type_timestamp,
<<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little, msec::32-little>>}

defp encode_param({year, month, day}, _binary_as),
do: {0, :field_type_date, << 4::8-little, year::16-little, month::8-little, day::8-little>>}
do: {0, :field_type_date, <<4::8-little, year::16-little, month::8-little, day::8-little>>}

defp encode_param({hour, min, sec, 0}, _binary_as),
do: {0, :field_type_time, << 8 :: 8-little, 0 :: 8-little, 0 :: 32-little, hour :: 8-little, min :: 8-little, sec :: 8-little >>}
do:
{0, :field_type_time,
<<8::8-little, 0::8-little, 0::32-little, hour::8-little, min::8-little, sec::8-little>>}

defp encode_param({hour, min, sec, msec}, _binary_as),
do: {0, :field_type_time, << 12 :: 8-little, 0 :: 8-little, 0 :: 32-little, hour :: 8-little, min :: 8-little, sec :: 8-little, msec :: 32-little>>}
do:
{0, :field_type_time,
<<12::8-little, 0::8-little, 0::32-little, hour::8-little, min::8-little, sec::8-little,
msec::32-little>>}

defp encode_param({{year, month, day}, {hour, min, sec}}, _binary_as),
do: {0, :field_type_datetime, << 7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little>>}
do:
{0, :field_type_datetime,
<<7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little>>}

defp encode_param({{year, month, day}, {hour, min, sec, 0}}, _binary_as),
do: {0, :field_type_datetime, << 7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little>>}
do:
{0, :field_type_datetime,
<<7::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little>>}

defp encode_param({{year, month, day}, {hour, min, sec, msec}}, _binary_as),
do: {0, :field_type_datetime, <<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little, min::8-little, sec::8-little, msec::32-little>>}
do:
{0, :field_type_datetime,
<<11::8-little, year::16-little, month::8-little, day::8-little, hour::8-little,
min::8-little, sec::8-little, msec::32-little>>}

defp encode_param(%Mariaex.Geometry.Point{coordinates: {x, y}, srid: srid}, _binary_as) do
srid = srid || 0
endian = 1 # MySQL is always little-endian
# MySQL is always little-endian
endian = 1
point_type = 1
{0, :field_type_geometry, << 25::8-little, srid::32-little, endian::8-little, point_type::32-little, x::little-float-64, y::little-float-64 >>}

{0, :field_type_geometry,
<<25::8-little, srid::32-little, endian::8-little, point_type::32-little, x::little-float-64,
y::little-float-64>>}
end

defp encode_param(%Mariaex.Geometry.LineString{coordinates: coordinates, srid: srid}, _binary_as) do
defp encode_param(
%Mariaex.Geometry.LineString{coordinates: coordinates, srid: srid},
_binary_as
) do
srid = srid || 0
endian = 1 # MySQL is always little-endian
# MySQL is always little-endian
endian = 1
linestring_type = 2
num_points = length(coordinates)
points = encode_coordinates(coordinates)
mysql_wkb = << srid::32-little, endian::8-little, linestring_type::32-little, num_points::little-32, points::binary >>

mysql_wkb =
<<srid::32-little, endian::8-little, linestring_type::32-little, num_points::little-32,
points::binary>>

encode_param(mysql_wkb, :field_type_geometry)
end

defp encode_param(%Mariaex.Geometry.Polygon{coordinates: coordinates, srid: srid}, _binary_as) do
srid = srid || 0
endian = 1 # MySQL is always little-endian
# MySQL is always little-endian
endian = 1
polygon_type = 3
num_rings = length(coordinates)
rings = encode_rings(coordinates)
mysql_wkb = << srid::32-little, endian::8-little, polygon_type::32-little, num_rings::little-32, rings::binary >>

mysql_wkb =
<<srid::32-little, endian::8-little, polygon_type::32-little, num_rings::little-32,
rings::binary>>

encode_param(mysql_wkb, :field_type_geometry)
end

defp encode_param(
%Mariaex.Geometry.MultiPolygon{coordinates: coordinates, srid: srid},
_binary_as
) do
srid = srid || 0
# MySQL is always little-endian
endian = 1
polygon_type = 6
num_rings = length(coordinates)
rings = encode_rings(coordinates)

mysql_wkb =
<<srid::32-little, endian::8-little, polygon_type::32-little, num_rings::little-32,
rings::binary>>

encode_param(mysql_wkb, :field_type_geometry)
end

defp encode_param(other, _binary_as),
do: raise ArgumentError, "query has invalid parameter #{inspect other}"
do: raise(ArgumentError, "query has invalid parameter #{inspect(other)}")

def decode(_, {res, nil}, _) do
%Mariaex.Result{res | rows: nil}
end

def decode(_, {res, columns}, opts) do
%Mariaex.Result{rows: rows} = res
decoded = do_decode(rows, opts)
Expand All @@ -194,6 +334,7 @@ defimpl DBConnection.Query, for: Mariaex.Query do
case Keyword.get(opts, :decode_mapper) do
nil ->
Enum.reverse(rows)

mapper when is_function(mapper, 1) ->
do_decode(rows, mapper, [])
end
Expand All @@ -202,22 +343,30 @@ defimpl DBConnection.Query, for: Mariaex.Query do
defp do_decode([row | rows], mapper, acc) do
do_decode(rows, mapper, [mapper.(row) | acc])
end

defp do_decode([], _, acc) do
acc
end

## Geometry Helpers

defp encode_rings(coordinates, acc \\ <<>>)

defp encode_rings([coordinates | rest], acc) do
encode_rings(rest, << acc::binary, length(coordinates)::little-32, encode_coordinates(coordinates)::binary >>)
encode_rings(
rest,
<<acc::binary, length(coordinates)::little-32, encode_coordinates(coordinates)::binary>>
)
end

defp encode_rings([], acc), do: acc

defp encode_coordinates(coordinates, acc \\ <<>>)

defp encode_coordinates([{x, y} | rest], acc) do
encode_coordinates(rest, << acc::binary, x::little-float-64, y::little-float-64 >>)
encode_coordinates(rest, <<acc::binary, x::little-float-64, y::little-float-64>>)
end

defp encode_coordinates([], acc), do: acc
end

Expand Down
Loading