Skip to content

Implement insert/select support for Geometry types (point, polygon, linestring) #191

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

Closed
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,27 @@ After you are done, run `mix deps.get` in your shell to fetch and compile Mariae

Important configuration, which depends on used charset for support unicode chars, see `:binary_as`
in `Mariaex.start_link/1`

## Data representation

MySQL Elixir
---------- ------
NULL nil
TINYINT 42
INT 42
BIGINT 42
FLOAT 42.0
DOUBLE 42.0
DECIMAL #Decimal<42.0> *
VARCHAR "eric"
TEXT "eric"
BLOB <<42>>
DATE %Date{year: 2013, month: 10, day: 12}
TIME %Time{hour: 0, minute: 37, second: 14} **
YEAR 2013
DATETIME %DateTime{year: 2013 month: 10, day: 12, hour: 0, minute: 37, second: 14} **
TIMESTAMP %DateTime{year: 2013 month: 10, day: 12, hour: 0, minute: 37, second: 14} **
BIT << 1 >>
GEOMETRY/POINT %Mariaex.Geometry.Point{coordinates: {1.0, -1.0}, srid: 42}
GEOMETRY/LINESTRING %Mariaex.Geometry.LineString{coordinates: [{0.0, 0.0}, {10.0, 10.0}, {20.0, 25.0}, {50.0, 60.0}], srid: 0}
GEOMETRY/POLYGON %Mariaex.Geometry.Polygon{coordinates: [[{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0}, {0.0, 0.0}], [{5.0, 5.0}, {7.0, 5.0}, {7.0, 7.0}, {5.0, 7.0}, {5.0, 5.0}]], srid: 0}
8 changes: 8 additions & 0 deletions lib/mariaex/geometry/line_string.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule Mariaex.Geometry.LineString do
@moduledoc """
Define the LineString struct
"""

@type t :: %Mariaex.Geometry.LineString{ coordinates: [{number, number}], srid: non_neg_integer | nil }
defstruct coordinates: [], srid: nil
end
8 changes: 8 additions & 0 deletions lib/mariaex/geometry/point.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule Mariaex.Geometry.Point do
@moduledoc """
Define the Point struct
"""

@type t :: %Mariaex.Geometry.Point{ coordinates: {number, number}, srid: non_neg_integer | nil }
defstruct coordinates: {0, 0}, srid: nil
end
8 changes: 8 additions & 0 deletions lib/mariaex/geometry/polygon.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule Mariaex.Geometry.Polygon do
@moduledoc """
Define the Polygon struct
"""

@type t :: %Mariaex.Geometry.Polygon{ coordinates: [[{number, number}]], srid: non_neg_integer | nil }
defstruct coordinates: [], srid: nil
end
2 changes: 2 additions & 0 deletions lib/mariaex/messages.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ defmodule Mariaex.Messages do
field_type_blob: 0xfc,
field_type_var_string: 0xfd,
field_type_string: 0xfe],
geometry:
[field_type_geometry: 0xff],
null:
[field_type_null: 0x06]
]
Expand Down
38 changes: 38 additions & 0 deletions lib/mariaex/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,30 @@ defimpl DBConnection.Query, for: Mariaex.Query do
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>>}
defp encode_param(%Mariaex.Geometry.Point{coordinates: {x, y}, srid: srid}, _binary_as) do
srid = srid || 0
endian = 1 # MySQL is always little-endian
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 >>}
end
defp encode_param(%Mariaex.Geometry.LineString{coordinates: coordinates, srid: srid}, _binary_as) do
srid = srid || 0
endian = 1 # MySQL is always little-endian
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 >>
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
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 >>
encode_param(mysql_wkb, :field_type_geometry)
end
defp encode_param(other, _binary_as),
do: raise ArgumentError, "query has invalid parameter #{inspect other}"

Expand Down Expand Up @@ -157,6 +181,20 @@ defimpl DBConnection.Query, for: Mariaex.Query do
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 >>)
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 >>)
end
defp encode_coordinates([], acc), do: acc
end

defimpl String.Chars, for: Mariaex.Query do
Expand Down
35 changes: 35 additions & 0 deletions lib/mariaex/row_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ defmodule Mariaex.RowParser do
defp type_to_atom({:float, :field_type_float}, _), do: :float32
defp type_to_atom({:float, :field_type_double}, _), do: :float64
defp type_to_atom({:bit, :field_type_bit}, _), do: :bit
defp type_to_atom({:geometry, :field_type_geometry}, _), do: :geometry
defp type_to_atom({:null, :field_type_null}, _), do: nil

defp decode_bin_rows(<<rest::bits>>, [_ | fields], nullint, acc) when (nullint &&& 1) === 1 do
Expand Down Expand Up @@ -146,6 +147,10 @@ defmodule Mariaex.RowParser do
decode_string(rest, fields, null_bitfield >>> 1, acc)
end

defp decode_bin_rows(<<rest::bits>>, [:geometry | fields], null_bitfield, acc) do
decode_geometry(rest, fields, null_bitfield >>> 1, acc)
end

defp decode_bin_rows(<<rest::bits>>, [:nil | fields], null_bitfield, acc) do
decode_bin_rows(rest, fields, null_bitfield >>> 1, [nil | acc])
end
Expand Down Expand Up @@ -271,6 +276,36 @@ defmodule Mariaex.RowParser do
decode_bin_rows(rest, fields, null_bitfield, [{{year, month, day}, {hour, min, sec, msec}} | acc])
end

defp decode_geometry(<<25::8-little, srid::32-little, 1::8-little, 1::32-little, x::little-float-64, y::little-float-64, rest::bits >>, fields, null_bitfield, acc) do
decode_bin_rows(rest, fields, null_bitfield, [%Mariaex.Geometry.Point{srid: srid, coordinates: {x, y}} | acc])
end
defp decode_geometry(<<_len::8-little, srid::32-little, 1::8-little, 2::32-little, num_points::32-little, points::binary-size(num_points)-unit(128), rest::bits >>, fields, null_bitfield, acc) do
coordinates = decode_points(points)
decode_bin_rows(rest, fields, null_bitfield, [%Mariaex.Geometry.LineString{srid: srid, coordinates: coordinates} | acc])
end
defp decode_geometry(<<_len::8-little, srid::32-little, 1::8-little, 3::32-little, num_rings::32-little, rest::bits >>, fields, null_bitfield, acc) do
decode_rings(rest, num_rings, {srid, fields, null_bitfield, acc})
end

### GEOMETRY HELPERS

defp decode_rings(<< rings_and_rows::bits >>, num_rings, state) do
decode_rings(rings_and_rows, num_rings, state, [])
end
defp decode_rings(<< rest::bits >>, 0, {srid, fields, null_bitfield, acc}, rings) do
decode_bin_rows(rest, fields, null_bitfield, [%Mariaex.Geometry.Polygon{coordinates: Enum.reverse(rings), srid: srid} | acc])
end
defp decode_rings(<< num_points::32-little, points::binary-size(num_points)-unit(128), rest::bits >>, num_rings, state, rings) do
points = decode_points(points)
decode_rings(rest, num_rings - 1, state, [points | rings])
end

defp decode_points(points_binary, points \\ [])
defp decode_points(<< x::little-float-64, y::little-float-64, rest::bits >>, points) do
decode_points(rest, [{x, y} | points])
end
defp decode_points(<<>>, points), do: Enum.reverse(points)

### TEXT ROW PARSER

def decode_text_init(columns) do
Expand Down
7 changes: 6 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ defmodule Mariaex.Mixfile do
source_url: "https://github.com/liveforeverx/mariaex",
test_coverage: [tool: Coverex.Task, coveralls: true],
description: description(),
package: package()]
package: package(),
docs: [
main: "Mariaex",
extras: ["README.md"]
]
]
end

# Configuration for the OTP application
Expand Down
29 changes: 14 additions & 15 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
%{"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
"coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
"decimal": {:hex, :decimal, "1.1.0", "3333732f17a90ff3057d7ab8c65f0930ca2d67e15cca812a91ead5633ed472fe", [:mix], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
"hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:rebar3, :mix], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"httpoison": {:hex, :httpoison, "0.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, optional: false]}]},
"idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:rebar, :make], []},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5", "2e73e068cd6393526f9fa6d399353d7c9477d6886ba005f323b592d389fb47be", [:make], []}}
%{"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"coverex": {:hex, :coverex, "1.4.10", "f6b68f95b3d51d04571a09dd2071c980e8398a38cf663db22b903ecad1083d51", [:mix], [{:httpoison, "~> 0.9", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.1.0", "3333732f17a90ff3057d7ab8c65f0930ca2d67e15cca812a91ead5633ed472fe", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.6.3", "d489d7ca2d4323e307bedc4bfe684323a7bf773ecfd77938f3ee8074e488e140", [:mix, :rebar3], [{:certifi, "0.7.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "1.2.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.10.0", "4727b3a5e57e9a4ff168a3c2883e20f1208103a41bccc4754f15a9366f49b676", [:mix], [{:hackney, "~> 1.6.3", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}}
Loading