From cab46b0917c3173c15a4e5f7384fbbeb4b33a2af Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:15:40 +0100 Subject: [PATCH 01/27] remove some unused branches --- lib/geo_sql/mm.ex | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/geo_sql/mm.ex b/lib/geo_sql/mm.ex index 7f2e6ae..796ff8d 100644 --- a/lib/geo_sql/mm.ex +++ b/lib/geo_sql/mm.ex @@ -376,14 +376,9 @@ defmodule GeoSQL.MM do end @doc group: "Geometry Accessors" - defmacro num_patches(geometry, repo \\ nil) do - case RepoUtils.adapter(repo) do - Ecto.Adapters.SQLite3 -> - quote do: fragment("NumGeometries(?)", unquote(geometry)) - - _ -> - quote do: fragment("ST_NumPatches(?)", unquote(geometry)) - end + # FIXME GeoSQL2: drop repo paramater + defmacro num_patches(geometry, _repo \\ nil) do + quote do: fragment("ST_NumGeometries(?)", unquote(geometry)) end @spec num_points(GeoSQL.geometry_input()) :: GeoSQL.fragment() @@ -411,14 +406,9 @@ defmodule GeoSQL.MM do ) :: GeoSQL.fragment() @doc group: "Geometry Accessors" - defmacro patch_n(geometry, face_index, repo \\ nil) do - case RepoUtils.adapter(repo) do - Ecto.Adapters.SQLite3 -> - quote do: fragment("ST_GeometryN(?,?)", unquote(geometry), unquote(face_index)) - - _ -> - quote do: fragment("ST_PatchN(?,?)", unquote(geometry), unquote(face_index)) - end + # FIXME GeoSQL2: drop repo paramater + defmacro patch_n(geometry, face_index, _repo \\ nil) do + quote do: fragment("ST_GeometryN(?,?)", unquote(geometry), unquote(face_index)) end @spec perimeter(GeoSQL.geometry_input()) :: GeoSQL.fragment() From a5ed7c7b8cd397d5f9275cd10882304651e33bd1 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:24:22 +0100 Subject: [PATCH 02/27] Add a function to raise a std "this feature unsupported by .." message --- lib/geo_sql/repo_utils.ex | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/geo_sql/repo_utils.ex b/lib/geo_sql/repo_utils.ex index cfc9977..f8402e0 100644 --- a/lib/geo_sql/repo_utils.ex +++ b/lib/geo_sql/repo_utils.ex @@ -55,4 +55,14 @@ defmodule GeoSQL.RepoUtils do end end end + + @doc false + def unsupported(fn_name, adapter) do + raise "#{fn_name} is unspported in #{adpter_to_name(adapter)}" + end + + defp adpter_to_name(Ecto.Adapters.Postgres), do: "PostGIS" + defp adpter_to_name(Ecto.Adapters.MyXQL), do: "MySQL/MariaDB" + defp adpter_to_name(Ecto.Adapters.SQLite3), do: "SpatiaLite" + defp adpter_to_name(adapter), do: adapter end From 1fea7f0f135fe314fb8cbe8485962adaa27b80f7 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:25:31 +0100 Subject: [PATCH 03/27] add an optional dep on myxql --- mix.exs | 12 +++++++++--- mix.lock | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 89e2b51..f46cb37 100644 --- a/mix.exs +++ b/mix.exs @@ -28,12 +28,17 @@ defmodule GeoSQL.Mixfile do defp deps do [ - {:geometry, "~> 1.1"}, - {:postgrex, ">= 0.0.0"}, {:ecto, "~> 3.13.0"}, {:ecto_sql, "~> 3.0"}, + {:geometry, "~> 1.1"}, + {:postgrex, ">= 0.0.0"}, {:exqlite, "~> 0.32"}, {:ecto_sqlite3, "~> 0.21"}, + {:myxql, + git: "https://github.com/aseigo/myxql.git", + branch: "feature/add-support-for-gepmetry-lib", + override: true, + optional: true}, {:jason, "~> 1.0"}, # dev @@ -78,7 +83,8 @@ defmodule GeoSQL.Mixfile do # TODO: would be nice to find a way to get this from the test config. defp dynamic_test_repos(command), - do: "#{command} --quiet -r GeoSQL.Test.PostGIS.Repo -r GeoSQL.Test.SpatiaLite.Repo" + do: + "#{command} --quiet -r GeoSQL.Test.PostGIS.Repo -r GeoSQL.Test.SpatiaLite.Repo -r GeoSQL.Test.MySQL.Repo" defp aliases do [ diff --git a/mix.lock b/mix.lock index e1f61a0..3d76e95 100644 --- a/mix.lock +++ b/mix.lock @@ -26,6 +26,7 @@ "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mix_test_watch": {:hex, :mix_test_watch, "1.4.0", "d88bcc4fbe3198871266e9d2f00cd8ae350938efbb11d3fa1da091586345adbb", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "2b4693e17c8ead2ef56d4f48a0329891e8c2d0d73752c0f09272a2b17dc38d1b"}, + "myxql": {:git, "https://github.com/aseigo/myxql.git", "808dcb6ad91bf6b180fdf11ba1e11d66fd091c51", [branch: "feature/add-support-for-gepmetry-lib"]}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, From 1d8c7860fae4f04877b295126b20aecdaa118f17 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:26:19 +0100 Subject: [PATCH 04/27] add myxql to the test matrix --- config/test.exs | 15 ++++++- .../migrations/20250603091627_initialize.exs | 42 +++++++++++++++++++ test/support/repo.ex | 9 ++++ test/test_helper.exs | 7 +++- 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 priv/repo/mysql/migrations/20250603091627_initialize.exs diff --git a/config/test.exs b/config/test.exs index 3038301..7a9a721 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,11 +1,12 @@ import Config test_repos = - (System.get_env("GEOSQL_TEST_BACKENDS") || "pgsql,sqlite3") + (System.get_env("GEOSQL_TEST_BACKENDS") || "pgsql,sqlite3,mysql") |> String.split(",") |> Enum.reduce([], fn backend, acc -> case backend do "pgsql" -> [GeoSQL.Test.PostGIS.Repo | acc] + "mysql" -> [GeoSQL.Test.MySQL.Repo | acc] "sqlite3" -> [GeoSQL.Test.SpatiaLite.Repo, GeoSQL.Test.Geopackage.Repo | acc] end end) @@ -19,6 +20,18 @@ config :geo_sql, GeoSQL.Test.PostGIS.Repo, priv: "priv/repo/postgis", testing_primary: true +config :geo_sql, GeoSQL.Test.MySQL.Repo, + host: "localhost", + username: "testing", + password: "testing", + protocol: :tcp, + database: "geosql_tests", + types: GeoSQL.PostgrexTypes, + pool: Ecto.Adapters.SQL.Sandbox, + priv: "priv/repo/mysql", + testing_primary: true, + show_sensitive_data_on_connection_error: true + config :geo_sql, GeoSQL.Test.SpatiaLite.Repo, database: "geo_sql_test.sqlite3", pool_size: 20, diff --git a/priv/repo/mysql/migrations/20250603091627_initialize.exs b/priv/repo/mysql/migrations/20250603091627_initialize.exs new file mode 100644 index 0000000..de7aa9c --- /dev/null +++ b/priv/repo/mysql/migrations/20250603091627_initialize.exs @@ -0,0 +1,42 @@ +defmodule GeoSQL.MySQL.Test.Repo.Migrations.Initialize do + use Ecto.Migration + + def change do + create table("locations") do + add(:name, :text) + add(:geom, :geometry) + end + + create table("geographies") do + add(:name, :text) + add(:geom, :geometry) + add(:line, :geometry) + end + + create table("location_multi") do + add(:name, :text) + add(:geom, :geometry) + end + + execute( + "CREATE TABLE specified_columns + ( + id int, + t text, + point geometry(4326), + pointz geometry(4326), + linestring geometry(4326), + linestringm geometry(4326), + linestringz geometry(4326), + linestringzm geometry(4326), + polygon geometry(4326), + polygonm geometry(4326), + multipoint geometry(4326), + multilinestring geometry(4326), + multipolygon geometry(4326) + ) + ", + "DROP TABLE specified_columns" + ) + end +end diff --git a/test/support/repo.ex b/test/support/repo.ex index fbf695d..6a4bc2b 100644 --- a/test/support/repo.ex +++ b/test/support/repo.ex @@ -5,6 +5,15 @@ defmodule GeoSQL.Test.PostGIS.Repo do def to_boolean(value), do: value end +defmodule GeoSQL.Test.MySQL.Repo do + use Ecto.Repo, otp_app: :geo_sql, adapter: Ecto.Adapters.MyXQL + + def has_array_literals?, do: true + def to_boolean(1), do: true + def to_boolean(0), do: false + def to_boolean(value), do: value +end + defmodule GeoSQL.Test.SpatiaLite.Repo do use Ecto.Repo, otp_app: :geo_sql, adapter: Ecto.Adapters.SQLite3 diff --git a/test/test_helper.exs b/test/test_helper.exs index 3abc609..a619e86 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -97,6 +97,7 @@ defmodule GeoSQL.Test.Helper do setup_funs = Keyword.get(options, :setup_funs, []) quote do + alias GeoSQL.Test.MySQL.Repo, as: MySQLRepo alias GeoSQL.Test.PostGIS.Repo, as: PostGISRepo alias GeoSQL.Test.SpatiaLite.Repo, as: SpatialiteRepo alias GeoSQL.Test.Geopackage.Repo, as: GeopackageRepo @@ -133,6 +134,7 @@ defmodule GeoSQL.Test.Helper do # A globally shared repo is problematic due to not wanting to polute the main library # or have a global ExUnit-wide global PID. So instead a repo is started per test suite. def repo_name_suffix(GeoSQL.Test.PostGIS.Repo), do: "PostGISRepo" + def repo_name_suffix(GeoSQL.Test.MySQL.Repo), do: "MySQL" def repo_name_suffix(GeoSQL.Test.SpatiaLite.Repo), do: "SpatiaLiteRepo" def repo_name_suffix(GeoSQL.Test.Geopackage.Repo), do: "GeopackageRepo" @@ -143,7 +145,10 @@ defmodule GeoSQL.Test.Helper do # horrible hack here, but we need to start repo supervised for setup_all functions # that need access to a global db object they can modify for all tests aftewards. - pid = ExUnit.Callbacks.start_link_supervised!(repo_spec) + pid = + ExUnit.Callbacks.start_link_supervised!(repo_spec) + |> IO.inspect(label: repo) + Ecto.Adapters.SQL.Sandbox.mode(pid, :manual) GeoSQL.init(repo, json: Jason, decode_binary: :reference) From 4d373e7bceec89a32238113ea2b16da60c78074e Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:26:50 +0100 Subject: [PATCH 05/27] add the geometry codec for myxql --- lib/geo_sql.ex | 6 ++++++ lib/geo_sql/mysql/geometry_codec.ex | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/geo_sql/mysql/geometry_codec.ex diff --git a/lib/geo_sql.ex b/lib/geo_sql.ex index 5a94cf8..b902cc5 100644 --- a/lib/geo_sql.ex +++ b/lib/geo_sql.ex @@ -71,6 +71,12 @@ defmodule GeoSQL do adapter end + if Code.ensure_loaded?(MyXQL) do + defp register_types(Ecto.Adapters.MyXQL = adapter, repo, opts) do + Application.put_env(:myxql, :geometry_codec, GeoSQL.MySQL.GeometryCodec) + end + end + defp register_types(adapter, _repo, _opts), do: adapter defp run_query(repo, sql) do diff --git a/lib/geo_sql/mysql/geometry_codec.ex b/lib/geo_sql/mysql/geometry_codec.ex new file mode 100644 index 0000000..50f7d2c --- /dev/null +++ b/lib/geo_sql/mysql/geometry_codec.ex @@ -0,0 +1,26 @@ +if Code.ensure_loaded?(MyXQL) do + defmodule GeoSQL.MySQL.GeometryCodec do + @behaviour MyXQL.Protocol.GeometryCodec + + supported_structs = [ + Geometry.Point, + Geometry.GeometryCollection, + Geometry.LineString, + Geometry.MultiPoint, + Geometry.MultiLineString, + Geometry.MultiPolygon, + Geometry.Polygon + ] + + def encode(%x{} = geo) when x in unquote(supported_structs) do + srid = geo.srid || 0 + wkb = Geometry.to_wkb(geo, :ndr) + {srid, wkb} + end + + def encode(_), do: :unknown + + def decode(0, wkb), do: Geometry.from_wkb!(wkb) + def decode(srid, wkb), do: Geometry.from_wkb!(wkb) |> Map.put(:srid, srid) + end +end From 2a7478415158523a1dbf7e64a9194f6ba8232f21 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:29:26 +0100 Subject: [PATCH 06/27] mysql does not require additional en/decoding of geometries --- lib/geo_sql/query_utils.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/geo_sql/query_utils.ex b/lib/geo_sql/query_utils.ex index c5d62ce..ceec8b8 100644 --- a/lib/geo_sql/query_utils.ex +++ b/lib/geo_sql/query_utils.ex @@ -130,6 +130,7 @@ defmodule GeoSQL.QueryUtils do case repo.__adapter__() do Ecto.Adapters.Postgres -> query_result Ecto.Adapters.SQLite3 -> decode_one(GeoSQL.SpatiaLite.TypeExtension, query_result) + Ecto.Adapters.MyXQL -> query_result end end @@ -143,6 +144,9 @@ defmodule GeoSQL.QueryUtils do query_results, fn query_result -> decode_one(GeoSQL.SpatiaLite.TypeExtension, query_result) end ) + + Ecto.Adapters.MyXQL -> + query_results end end @@ -191,6 +195,9 @@ defmodule GeoSQL.QueryUtils do Ecto.Adapters.SQLite3 -> decode_all(query_results, GeoSQL.SpatiaLite.TypeExtension, fields_to_decode) + + Ecto.Adapters.MyXQL -> + query_results end end From ca2cd03cc73d57046390d948685fb50c6c38bd3f Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:29:53 +0100 Subject: [PATCH 07/27] mark a few unsupported functions --- lib/geo_sql/mm.ex | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/geo_sql/mm.ex b/lib/geo_sql/mm.ex index 796ff8d..de5b749 100644 --- a/lib/geo_sql/mm.ex +++ b/lib/geo_sql/mm.ex @@ -100,11 +100,11 @@ defmodule GeoSQL.MM do """ defmacro coord_dim(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.SQLite3 -> - quote do: fragment("ST_NDims(?)", unquote(geometry)) + Ecto.Adapters.MyXQL -> + RepoUtils.raise("coord_dim", Ecto.Adapters.MyXQL) _ -> - quote do: fragment("ST_CoordDim(?)", unquote(geometry)) + quote do: fragment("ST_NDims(?)", unquote(geometry)) end end @@ -226,11 +226,14 @@ defmodule GeoSQL.MM do @doc group: "Data Formats" defmacro gml_to_sql(geomgml, repo \\ nil) do case RepoUtils.adapter(repo) do + Ecto.Adapters.Postgres -> + quote do: fragment("ST_GMLToSQL(?)", unquote(geomgml)) + Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromGML(?)", unquote(geomgml)) - _ -> - quote do: fragment("ST_GMLToSQL(?)", unquote(geomgml)) + adapter -> + RepoUtils.unsupported("gml_to_sql", adapter) end end @@ -242,11 +245,14 @@ defmodule GeoSQL.MM do @doc group: "Data Formats" defmacro gml_to_sql(geomgml, srid, repo) do case RepoUtils.adapter(repo) do + Ecto.Adapters.Postgres -> + quote do: fragment("ST_GMLToSQL(?,?)", unquote(geomgml), unquote(srid)) + Ecto.Adapters.SQLite3 -> quote do: fragment("SetSRID(GeomFromGML(?), ?)", unquote(geomgml), unquote(srid)) - _ -> - quote do: fragment("ST_GMLToSQL(?,?)", unquote(geomgml), unquote(srid)) + adapter -> + RepoUtils.unsupported("gml_to_sql", adapter) end end From b3f181c773d0e6f33a8e527806ffc157c2d252db Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:43:42 +0100 Subject: [PATCH 08/27] spiffy up --- README.md | 117 +++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index eb168ac..3f67c6d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ level functions for features such as generating Mapbox vector tiles. The goals of this library are: * Ease: fast to get started, hide complexity where possible - * Portability: currently supports PostGIS and SpatiaLite. + * Portability: currently supports PostGIS, SpatiaLite, MariaDB/MySQL, and GeoPackage. * Completeness: extensive support for GIS SQL functions, not just the most common ones. * Clarity: Functions organized by their availability and standards compliance * Utility: Provide out-of-the-box support for complete worfklows. Mapbox vector tile @@ -21,19 +21,19 @@ The goals of this library are: Not-goals include: * Having the fewest possible dependencies. Ecto adapters are pulled in as necessary, - along with other dependencies such as `Jason` in order to ease use. + and other quality-of-life libraries may also be used internally. ## Usage Add `GeoSQL` to your project by adding the following to the `deps` section in `mix.exs` (or equivalent): - ``` + ```elixir {:geo_sql, "~> 0.1"} ``` -Run the usual `mix deps.get`! +and then run the usual `mix deps.get`. -Full documentation can be generated locally with `mix docs`. +Full documentation is available [online](https://hexdocs.pm/geo_sql). ### Ecto Schemas @@ -75,45 +75,7 @@ Example: end ``` -#### Geo - -If the `geo` library is included in the build, `%Geo.{}` stucts are supported for writes to -the database. Libraries and Ecto schemas which still use `%Geo.{}` structs can therefore be -used with `GeoSQL`'s database extentions. - -#### Geopackage - -The Geopackage standard defines its own binary format for serializing geometries in SQLite3 -databases. GeoSQL provides access to these types via the `GeoSQL.Geometry.Geopackage` type -allowing Ecto schemas to contain geometry fields that are stored as Geopackage data. - -Example: - - ```elixir - defmodule MyApp.Geopackage do - use Ecto.Schema - - @primary_key false - schema "some_table_in_geopackage" do - field(:id, :integer, source: :OBJECTID) - field(:name, :string) - field(:shape, GeoSQL.Geometry.Geopackage, source: :Shape) - end - end - ``` - -This schema can now be used in queries like any other: - - ```elixir - from(g in MyApp.Geopackage) |> MyApp.GeopackageRepo.all() - ``` - -Due to being tied to SQLite3, only SQLite3 databases are supported and the Spatialite -module must be available as it is (currently) used to do the serialization in memory. -The Spatialite module does not need to be initialized on a Geopackage database, but -it does need to be available on the system for GeoSQL to use. - -### Readying the Repo with `GeoSQL.init/1` +### Readying an Ecto Repo with `GeoSQL.init/1` Once added to your project, an `Ecto.Repo` can be readied for use by calling `GeoSQL.init/2`. This can be done once the repo has been started by implementing @@ -154,7 +116,7 @@ will still need to be called after the repo has been started. Dynamic Ecto repositories are also supported, and `GeoSQL.init/1` can be called after the call to `Repo.put_dynamic_repo/1` has completed. -### Macro usage +### Ecto Queries Once initialized, the wide array of macros can be used with `Ecto` queries: @@ -348,7 +310,50 @@ with the `MVT` vector tile layer format. Database prefixes ("schemas" in PostgreSQL) are also supported both on the whole tile query as well as per-layer. -## Building +#### Geopackage + +The Geopackage standard defines its own binary format for serializing geometries in SQLite3 +databases. GeoSQL provides access to these types via the `GeoSQL.Geometry.Geopackage` type +allowing Ecto schemas to contain geometry fields that are stored as Geopackage data. + +Example: + + ```elixir + defmodule MyApp.Geopackage do + use Ecto.Schema + + @primary_key false + schema "some_table_in_geopackage" do + field(:id, :integer, source: :OBJECTID) + field(:name, :string) + field(:shape, GeoSQL.Geometry.Geopackage, source: :Shape) + end + end + ``` + +This schema can now be used in queries like any other: + + ```elixir + from(g in MyApp.Geopackage) |> MyApp.GeopackageRepo.all() + ``` + +Due to being tied to SQLite3, only SQLite3 databases are supported and the Spatialite +module must be available as it is (currently) used to do the serialization in memory. +The Spatialite module does not need to be initialized on a Geopackage database, but +it does need to be available on the system for GeoSQL to use. + +#### Support for the Geo library + +If the application includes the `geo` library as a dependency, `%Geo.{}` stucts are supported for writes to +the database. Libraries and Ecto schemas which still use `%Geo.{}` structs can therefore be +used with `GeoSQL`'s database extentions. + +## Contributing + +If you would like to contribute support for more functions (PostGIS and +SpatiaLite both provide a frighteningly impressive amount of them!) or +support for other databases, do not hesitate to make a PR and it will be +reviewed in a timely fashion. To build and interact with the library locally: @@ -358,14 +363,16 @@ To build and interact with the library locally: mix compile iex -S mix +Documentation can be generated locally with `mix docs`. + +### Unit Tests -## Unit Tests - -Unit tests currently assume a working PostGIS installation is available locally. +Unit tests currently assume that working installations of PostGIS, SpatiaLite, and MariaDB/Mysql are running on the local machine. -The URL for the test database is defined in `config/test.exs`. +The locations of the test databases are defined in `config/test.exs` and may be +changed to suit your own dev environment. -**Note** that this database will be created and **dropped** on every run of the tests. +**Note** that databases will be created and **dropped** on every run of the tests. Do NOT point it to an existing database! The migrations in `priv/repo/migrations` are run on each test run. @@ -390,13 +397,7 @@ Current the following backends are recognized: * `pgsql` * `sqlite3` - -## Contributing - -If you would like to contribute support for more functions (PostGIS and -SpatiaLite both provide a frighteningly impressive amount of them!) or -support for other databases, do not hesitate to make a PR and the author -will review and merge in a timely fashion. + * `mysql` ## Acknowledgements From fddf366c40946bc880f9d28386ce344db6fc504b Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Tue, 13 Jan 2026 14:45:26 +0100 Subject: [PATCH 09/27] update the version recommendation in the README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f67c6d..af93673 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Not-goals include: Add `GeoSQL` to your project by adding the following to the `deps` section in `mix.exs` (or equivalent): ```elixir - {:geo_sql, "~> 0.1"} + {:geo_sql, "~> 1.0"} ``` and then run the usual `mix deps.get`. From b3cc8bc75cc1345e48d7e723bd19f00674fc8a2d Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Fri, 16 Jan 2026 09:38:52 +0100 Subject: [PATCH 10/27] accommodate MysSQL in common queries --- lib/geo_sql/common.ex | 260 +++++++++++++++++++++--------------------- 1 file changed, 132 insertions(+), 128 deletions(-) diff --git a/lib/geo_sql/common.ex b/lib/geo_sql/common.ex index 9fdcd74..ac3870f 100644 --- a/lib/geo_sql/common.ex +++ b/lib/geo_sql/common.ex @@ -78,11 +78,11 @@ defmodule GeoSQL.Common do @doc group: "Well-Known Binary (WKB)" defmacro as_ewkb(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_AsEWKB(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("unhex(AsEWKB(?))", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_AsEWKB(?)", unquote(geometry)) end end @@ -90,11 +90,11 @@ defmodule GeoSQL.Common do @doc group: "Well-Known Text (WKT)" defmacro as_ewkt(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_AsEWKT(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("AsEWKT(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_AsEWKT(?)", unquote(geometry)) end end @@ -103,11 +103,11 @@ defmodule GeoSQL.Common do @doc group: "Data Formats" defmacro as_geojson(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_AsGeoJSON(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("AsGeoJSON(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_AsGeoJSON(?)", unquote(geometry)) end end @@ -121,7 +121,12 @@ defmodule GeoSQL.Common do @doc group: "Data Formats" defmacro as_gml(geometry, gml_version, precision, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> + quote do + fragment("AsGML(?,?,?)", unquote(gml_version), unquote(geometry), unquote(precision)) + end + + _adapter -> quote do fragment( "ST_AsGML(?,?,?)", @@ -130,11 +135,6 @@ defmodule GeoSQL.Common do unquote(precision) ) end - - Ecto.Adapters.SQLite3 -> - quote do - fragment("AsGML(?,?,?)", unquote(gml_version), unquote(geometry), unquote(precision)) - end end end @@ -148,14 +148,14 @@ defmodule GeoSQL.Common do @doc group: "Data Formats" defmacro as_kml(geometry, precision, name \\ "", repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> quote do - fragment("ST_AsKML(?,?,?)", unquote(geometry), unquote(precision), unquote(name)) + fragment("AsKML(?,'',?,?)", unquote(name), unquote(geometry), unquote(precision)) end - Ecto.Adapters.SQLite3 -> + _adapter -> quote do - fragment("AsKML(?,'',?,?)", unquote(name), unquote(geometry), unquote(precision)) + fragment("ST_AsKML(?,?,?)", unquote(geometry), unquote(precision), unquote(name)) end end end @@ -271,7 +271,10 @@ defmodule GeoSQL.Common do defmacro estimated_extent({schema, table}, column, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> + quote do: fragment("GetLayerExtent(?, ?)", unquote(table), unquote(column)) + + _adapter -> quote do fragment( "ST_EstimatedExtent(?, ?, ?)::geometry", @@ -280,19 +283,16 @@ defmodule GeoSQL.Common do unquote(column) ) end - - Ecto.Adapters.SQLite3 -> - quote do: fragment("GetLayerExtent(?, ?)", unquote(table), unquote(column)) end end defmacro estimated_extent(table, column, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_EstimatedExtent(?, ?)::geometry", unquote(table), unquote(column)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GetLayerExtent(?, ?)", unquote(table), unquote(column)) + + _adapter -> + quote do: fragment("ST_EstimatedExtent(?, ?)::geometry", unquote(table), unquote(column)) end end @@ -309,8 +309,8 @@ defmodule GeoSQL.Common do @doc group: "Bounding Boxes" defmacro extent(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_Extent(?)::geometry", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("extent(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_Extent(?)::geometry", unquote(geometry)) end end @@ -324,8 +324,8 @@ defmodule GeoSQL.Common do @doc group: "Geometry Mutations" defmacro flip_coordinates(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_FlipCoordinates(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("SwapCoordinates(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_FlipCoordinates(?)", unquote(geometry)) end end @@ -333,11 +333,11 @@ defmodule GeoSQL.Common do @doc group: "Well-Known Binary (WKB)" defmacro geom_from_ewkb(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GeomFromEWKB(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromEWKB(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_GeomFromEWKB(?)", unquote(geometry)) end end @@ -345,11 +345,11 @@ defmodule GeoSQL.Common do @doc group: "Well-Known Text (WKT)" defmacro geom_from_ewkt(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GeomFromEWKT(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromEWKT(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_GeomFromEWKT(?)", unquote(geometry)) end end @@ -358,11 +358,11 @@ defmodule GeoSQL.Common do @doc group: "Data Formats" defmacro geom_from_geojson(geojson, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GeomFromGeoJSON(?)", unquote(geojson)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromGeoJSON(?)", unquote(geojson)) + + _adapter -> + quote do: fragment("ST_GeomFromGeoJSON(?)", unquote(geojson)) end end @@ -376,15 +376,15 @@ defmodule GeoSQL.Common do @doc "Passing an srid of 0 (the default) will not set an srid on the results. It is ignored for Spatialite in all cases." defmacro geom_from_gml(gml, srid \\ 0, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> + quote do: fragment("GeomFromGML(?)", unquote(gml)) + + _adapter -> if srid > 0 do quote do: fragment("ST_GeomFromGML(?,?)", unquote(gml), unquote(srid)) else quote do: fragment("ST_GeomFromGML(?)", unquote(gml)) end - - Ecto.Adapters.SQLite3 -> - quote do: fragment("GeomFromGML(?)", unquote(gml)) end end @@ -393,11 +393,11 @@ defmodule GeoSQL.Common do @doc group: "Data Formats" defmacro geom_from_kml(kml, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GeomFromKML(?)", unquote(kml)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromKML(?)", unquote(kml)) + + _adapter -> + quote do: fragment("ST_GeomFromKML(?)", unquote(kml)) end end @@ -444,11 +444,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Processing" defmacro largest_empty_circle(geometry, tolerance \\ 0.0, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_LargestEmptyCircle(?,?)", unquote(geometry), unquote(tolerance)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GEOSLargestEmptyCircle(?,?)", unquote(geometry), unquote(tolerance)) + + _adapter -> + quote do: fragment("ST_LargestEmptyCircle(?,?)", unquote(geometry), unquote(tolerance)) end end @@ -466,7 +466,10 @@ defmodule GeoSQL.Common do """ defmacro line_interpolate_point(line, fraction, use_spheroid? \\ false, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> + quote do: fragment("ST_Line_Interpolate_Point(?,?)", unquote(line), unquote(fraction)) + + _adapter -> quote do fragment( "ST_LineInterpolatePoint(?,?,?)", @@ -475,9 +478,6 @@ defmodule GeoSQL.Common do unquote(use_spheroid?) ) end - - Ecto.Adapters.SQLite3 -> - quote do: fragment("ST_Line_Interpolate_Point(?,?)", unquote(line), unquote(fraction)) end end @@ -499,23 +499,23 @@ defmodule GeoSQL.Common do repo \\ nil ) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> quote do fragment( - "ST_LineInterpolatePoints(?,?,?,?)", + "ST_Line_Interpolate_Equidistant_Points(?,?)", unquote(line), - unquote(fraction), - unquote(use_spheroid?), - unquote(repeat?) + unquote(fraction) ) end - Ecto.Adapters.SQLite3 -> + _adapter -> quote do fragment( - "ST_Line_Interpolate_Equidistant_Points(?,?)", + "ST_LineInterpolatePoints(?,?,?,?)", unquote(line), - unquote(fraction) + unquote(fraction), + unquote(use_spheroid?), + unquote(repeat?) ) end end @@ -535,7 +535,10 @@ defmodule GeoSQL.Common do """ defmacro line_locate_point(line, point, use_spheroid? \\ false, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> + quote do: fragment("ST_Line_Locate_Point(?,?)", unquote(line), unquote(point)) + + _adapter -> quote do fragment( "ST_LineLocatePoint(?,?,?)", @@ -544,9 +547,6 @@ defmodule GeoSQL.Common do unquote(use_spheroid?) ) end - - Ecto.Adapters.SQLite3 -> - quote do: fragment("ST_Line_Locate_Point(?,?)", unquote(line), unquote(point)) end end @@ -560,20 +560,20 @@ defmodule GeoSQL.Common do @doc group: "Linear Referencing" defmacro line_substring(line, start_fraction, end_fraction, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> quote do fragment( - "ST_LineSubstring(?,?,?)", + "ST_Line_Substring(?,?,?)", unquote(line), unquote(start_fraction), unquote(end_fraction) ) end - Ecto.Adapters.SQLite3 -> + _adapter -> quote do fragment( - "ST_Line_Substring(?,?,?)", + "ST_LineSubstring(?,?,?)", unquote(line), unquote(start_fraction), unquote(end_fraction) @@ -621,11 +621,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Constructors" defmacro make_point(x, y, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MakePoint(?,?)", unquote(x), unquote(y)) - Ecto.Adapters.SQLite3 -> quote do: fragment("MakePoint(?,?)", unquote(x), unquote(y)) + + _adapter -> + quote do: fragment("ST_MakePoint(?,?)", unquote(x), unquote(y)) end end @@ -638,11 +638,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Constructors" defmacro make_point_z(x, y, z, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MakePoint(?,?,?)", unquote(x), unquote(y), unquote(z)) - Ecto.Adapters.SQLite3 -> quote do: fragment("MakePointZ(?,?,?)", unquote(x), unquote(y), unquote(z)) + + _adapter -> + quote do: fragment("ST_MakePoint(?,?,?)", unquote(x), unquote(y), unquote(z)) end end @@ -656,14 +656,14 @@ defmodule GeoSQL.Common do @doc group: "Geometry Constructors" defmacro make_point_zm(x, y, z, m, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> quote do - fragment("ST_MakePoint(?,?,?,?)", unquote(x), unquote(y), unquote(z), unquote(m)) + fragment("MakePointZM(?,?,?,?)", unquote(x), unquote(y), unquote(z), unquote(m)) end - Ecto.Adapters.SQLite3 -> + _adapter -> quote do - fragment("MakePointZM(?,?,?,?)", unquote(x), unquote(y), unquote(z), unquote(m)) + fragment("ST_MakePoint(?,?,?,?)", unquote(x), unquote(y), unquote(z), unquote(m)) end end end @@ -677,11 +677,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Constructors" defmacro make_point_m(x, y, z, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MakePointM(?,?,?)", unquote(x), unquote(y), unquote(z)) - Ecto.Adapters.SQLite3 -> quote do: fragment("MakePointM(?,?,?)", unquote(x), unquote(y), unquote(z)) + + _adapter -> + quote do: fragment("ST_MakePointM(?,?,?)", unquote(x), unquote(y), unquote(z)) end end @@ -697,22 +697,22 @@ defmodule GeoSQL.Common do defmacro max_coord(geometry, :x, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_XMax(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MaxX(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_XMax(?)", unquote(geometry)) end end defmacro max_coord(geometry, :y, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_YMax(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MaxY(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_YMax(?)", unquote(geometry)) end end defmacro max_coord(geometry, :z, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_ZMax(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MaxZ(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_ZMax(?)", unquote(geometry)) end end @@ -726,11 +726,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Processing" defmacro maximum_inscribed_circle(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MaximumInscribedCircle(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GEOSMaximumInscribedCircle(?, 0.0)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_MaximumInscribedCircle(?)", unquote(geometry)) end end @@ -741,22 +741,22 @@ defmodule GeoSQL.Common do defmacro min_coord(geometry, :x, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_XMin(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MinX(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_XMin(?)", unquote(geometry)) end end defmacro min_coord(geometry, :y, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_YMin(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MinY(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_YMin(?)", unquote(geometry)) end end defmacro min_coord(geometry, :z, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_ZMin(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_MinZ(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_ZMin(?)", unquote(geometry)) end end @@ -764,11 +764,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Processing" defmacro minimum_bounding_circle(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MinimumBoundingCircle(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GEOSMinimumBoundingCircle(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_MinimumBoundingCircle(?)", unquote(geometry)) end end @@ -781,19 +781,19 @@ defmodule GeoSQL.Common do @doc group: "Measurement" defmacro minimum_clearance(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_MinimumClearance(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("GEOSMinimumClearance(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_MinimumClearance(?)", unquote(geometry)) end end @doc group: "Measurement" defmacro minimum_clearance_line(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_MinimumClearanceLine(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GEOSMinimumClearanceLine(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_MinimumClearanceLine(?)", unquote(geometry)) end end @@ -885,11 +885,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Editors" defmacro remove_repeated_points(geometry, tolerance \\ 0, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_RemoveRepeatedPoints(?,?)", unquote(geometry), unquote(tolerance)) - Ecto.Adapters.SQLite3 -> quote do: fragment("RemoveRepeatedPoints(?,?)", unquote(geometry), unquote(tolerance)) + + _adapter -> + quote do: fragment("ST_RemoveRepeatedPoints(?,?)", unquote(geometry), unquote(tolerance)) end end @@ -907,9 +907,6 @@ defmodule GeoSQL.Common do @doc group: "Affine Transformations" defmacro rotate(geometry, rotate_radians, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_Rotate(?, ?)", unquote(geometry), unquote(rotate_radians)) - Ecto.Adapters.SQLite3 -> # SpatiaLite uses degrees :/ quote do @@ -919,6 +916,9 @@ defmodule GeoSQL.Common do fragment("Degrees(?)", unquote(rotate_radians)) ) end + + _adapter -> + quote do: fragment("ST_Rotate(?, ?)", unquote(geometry), unquote(rotate_radians)) end end @@ -926,17 +926,21 @@ defmodule GeoSQL.Common do GeoSQL.fragment() @doc group: "Affine Transformations" defmacro scale(geometry, scale_x, scale_y, repo \\ nil) do - if RepoUtils.adapter(repo) == Ecto.Adapters.SQLite3 do - quote do - fragment( - "ScaleCoordinates(?,?,?)", - unquote(geometry), - unquote(scale_x), - unquote(scale_y) - ) - end - else - quote do: fragment("ST_Scale(?,?,?)", unquote(geometry), unquote(scale_x), unquote(scale_y)) + case RepoUtils.adapter(repo) do + Ecto.Adapters.SQLite3 -> + quote do + fragment( + "ScaleCoordinates(?,?,?)", + unquote(geometry), + unquote(scale_x), + unquote(scale_y) + ) + end + + _adapter -> + quote do + fragment("ST_Scale(?,?,?)", unquote(geometry), unquote(scale_x), unquote(scale_y)) + end end end @@ -967,11 +971,11 @@ defmodule GeoSQL.Common do @doc group: "Spatial Reference Systems" defmacro set_srid(geometry, srid, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_SetSRID(?, ?)", unquote(geometry), unquote(srid)) - Ecto.Adapters.SQLite3 -> quote do: fragment("SetSRID(?, ?)", unquote(geometry), unquote(srid)) + + _adapter -> + quote do: fragment("ST_SetSRID(?, ?)", unquote(geometry), unquote(srid)) end end @@ -984,8 +988,8 @@ defmodule GeoSQL.Common do @doc group: "Geometry Mutations" defmacro shift_longitude(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> quote do: fragment("ST_ShiftLongitude(?)", unquote(geometry)) Ecto.Adapters.SQLite3 -> quote do: fragment("ST_Shift_Longitude(?)", unquote(geometry)) + _adapter -> quote do: fragment("ST_ShiftLongitude(?)", unquote(geometry)) end end @@ -1128,23 +1132,23 @@ defmodule GeoSQL.Common do @doc group: "Spatial Reference Systems" defmacro transform_pipeline(geometry, pipeline, srid, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> + Ecto.Adapters.SQLite3 -> quote do fragment( - "ST_TransformPipeline(?,?,?)", + "ST_Transform(?,?,NULL,?)", unquote(geometry), - unquote(pipeline), - unquote(srid) + unquote(srid), + unquote(pipeline) ) end - Ecto.Adapters.SQLite3 -> + _adapter -> quote do fragment( - "ST_Transform(?,?,NULL,?)", + "ST_TransformPipeline(?,?,?)", unquote(geometry), - unquote(srid), - unquote(pipeline) + unquote(pipeline), + unquote(srid) ) end end @@ -1154,11 +1158,11 @@ defmodule GeoSQL.Common do @doc group: "Geometry Processing" defmacro triangulate_polygon(geometry, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_TriangulatePolygon(?)", unquote(geometry)) - Ecto.Adapters.SQLite3 -> quote do: fragment("ConstrainedDelaunayTriangulation(?)", unquote(geometry)) + + _adapter -> + quote do: fragment("ST_TriangulatePolygon(?)", unquote(geometry)) end end From e0ec3e1d706f6c056a28a1b22534da5da6ce4067 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Fri, 16 Jan 2026 09:39:16 +0100 Subject: [PATCH 11/27] accommodate MysSQL in MM queries --- lib/geo_sql/mm.ex | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/geo_sql/mm.ex b/lib/geo_sql/mm.ex index de5b749..463482a 100644 --- a/lib/geo_sql/mm.ex +++ b/lib/geo_sql/mm.ex @@ -98,14 +98,9 @@ defmodule GeoSQL.MM do Note: this function takes an optional Ecto.Repo parameter due to some backends implementing the non-standard ST_NDims rather than ST_CoordDim """ - defmacro coord_dim(geometry, repo \\ nil) do - case RepoUtils.adapter(repo) do - Ecto.Adapters.MyXQL -> - RepoUtils.raise("coord_dim", Ecto.Adapters.MyXQL) - - _ -> - quote do: fragment("ST_NDims(?)", unquote(geometry)) - end + # FIXME GeoSQL2 remove unused repo param + defmacro coord_dim(geometry, _repo \\ nil) do + quote do: fragment("ST_NDims(?)", unquote(geometry)) end @spec crosses(GeoSQL.geometry_input(), GeoSQL.geometry_input()) :: GeoSQL.fragment() @@ -226,14 +221,11 @@ defmodule GeoSQL.MM do @doc group: "Data Formats" defmacro gml_to_sql(geomgml, repo \\ nil) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GMLToSQL(?)", unquote(geomgml)) - Ecto.Adapters.SQLite3 -> quote do: fragment("GeomFromGML(?)", unquote(geomgml)) - adapter -> - RepoUtils.unsupported("gml_to_sql", adapter) + _adapter -> + quote do: fragment("ST_GMLToSQL(?)", unquote(geomgml)) end end @@ -245,14 +237,11 @@ defmodule GeoSQL.MM do @doc group: "Data Formats" defmacro gml_to_sql(geomgml, srid, repo) do case RepoUtils.adapter(repo) do - Ecto.Adapters.Postgres -> - quote do: fragment("ST_GMLToSQL(?,?)", unquote(geomgml), unquote(srid)) - Ecto.Adapters.SQLite3 -> quote do: fragment("SetSRID(GeomFromGML(?), ?)", unquote(geomgml), unquote(srid)) - adapter -> - RepoUtils.unsupported("gml_to_sql", adapter) + _adapter -> + quote do: fragment("ST_GMLToSQL(?,?)", unquote(geomgml), unquote(srid)) end end From 4500caef725baf8c803dd2df40d6810cadfc0582 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 14:18:20 +0100 Subject: [PATCH 12/27] bump version to 0.8.1, update CHANGELOG and description --- CHANGELOG.md | 4 ++++ mix.exs | 10 +++------- mix.lock | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f127660..0238773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.8.0] 11-03-2026 + +* Improvements + * MySQL / MariaDB is now supported via a MyXQL extension. ## [1.7.0] 14-02-2026 * Dependencies diff --git a/mix.exs b/mix.exs index f46cb37..31cd2b9 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule GeoSQL.Mixfile do use Mix.Project @source_url "https://github.com/expothecary/geo_sql" - @version "1.7.0" + @version "1.8.0" def project do [ @@ -34,11 +34,7 @@ defmodule GeoSQL.Mixfile do {:postgrex, ">= 0.0.0"}, {:exqlite, "~> 0.32"}, {:ecto_sqlite3, "~> 0.21"}, - {:myxql, - git: "https://github.com/aseigo/myxql.git", - branch: "feature/add-support-for-gepmetry-lib", - override: true, - optional: true}, + {:myxql, "~> 0.8.1", optional: true}, {:jason, "~> 1.0"}, # dev @@ -55,7 +51,7 @@ defmodule GeoSQL.Mixfile do defp package do [ description: - "Spatial databases and GIS SQL functions. Currently supports PostGIS, Spatialite, and Geopackage.", + "Spatial databases and GIS SQL functions. Currently supports PostGIS, Spatialite, MySQL, MariaDB, and Geopackage.", files: ["lib", "mix.exs", "README.md", "CHANGELOG.md"], maintainers: ["Aaron Seigo"], licenses: ["MIT"], diff --git a/mix.lock b/mix.lock index 3d76e95..277710d 100644 --- a/mix.lock +++ b/mix.lock @@ -26,7 +26,7 @@ "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "mix_test_watch": {:hex, :mix_test_watch, "1.4.0", "d88bcc4fbe3198871266e9d2f00cd8ae350938efbb11d3fa1da091586345adbb", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "2b4693e17c8ead2ef56d4f48a0329891e8c2d0d73752c0f09272a2b17dc38d1b"}, - "myxql": {:git, "https://github.com/aseigo/myxql.git", "808dcb6ad91bf6b180fdf11ba1e11d66fd091c51", [branch: "feature/add-support-for-gepmetry-lib"]}, + "myxql": {:hex, :myxql, "0.8.1", "5d7b96f288a98927a40309b274917d16a288a8c0b273205135913ff9799563b3", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4 or ~> 4.0", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "85a4795712bbab1a0f0803d5f0c7332bb383e5f07d3443a42e17a9aa996bbddb"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, From bf3054194f4166483d6769be2da671b67a12e618 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 14:19:31 +0100 Subject: [PATCH 13/27] add CI workflow --- .github/workflows/ci.yml | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9b93203 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +# Created with GitHubActions version 0.3.9 +name: CI +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +on: + - pull_request +jobs: + linux: + name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) + runs-on: ubuntu-24.04 + strategy: + matrix: + include: + - elixir: '1.19.1' + otp: '28.1' + coverage: true + - elixir: '1.19.1' + otp: '27.3' + - elixir: '1.19.1' + otp: '26.2' + - elixir: '1.18.4' + otp: '28.1' + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Elixir + id: setup-beam + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + - name: Restore deps + uses: actions/cache@v4 + with: + path: deps + key: "deps\ + -${{ runner.os }}\ + -${{ matrix.elixir }}\ + -${{ matrix.otp }}\ + -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ + -${{ steps.setup-beam.outputs.setup-beam-version }}" + - name: Restore _build + uses: actions/cache@v4 + with: + path: _build + key: "_build\ + -${{ runner.os }}\ + -${{ matrix.elixir }}\ + -${{ matrix.otp }}\ + -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ + -${{ steps.setup-beam.outputs.setup-beam-version }}" + - name: Restore test/support/plts + if: ${{ matrix.lint }} + uses: actions/cache@v4 + with: + path: test/support/plts + key: "test/support/plts\ + -${{ runner.os }}\ + -${{ matrix.elixir }}\ + -${{ matrix.otp }}\ + -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ + -${{ steps.setup-beam.outputs.setup-beam-version }}" + - name: Get dependencies + run: mix deps.get + - name: Compile dependencies + run: MIX_ENV=test mix deps.compile + - name: Compile project + run: MIX_ENV=test mix compile --warnings-as-errors + - name: Check unused dependencies + if: ${{ matrix.lint }} + run: mix deps.unlock --check-unused + - name: Check code format + if: ${{ matrix.lint }} + run: mix format --check-formatted + - name: Lint code + if: ${{ matrix.lint }} + run: mix credo --strict + - name: Run tests + if: ${{ !matrix.coverage }} + run: mix test + - name: Run tests with coverage + if: ${{ matrix.coverage }} + run: mix coveralls.github + - name: Static code analysis + if: ${{ matrix.lint }} + run: mix dialyzer --format github --force-check From 406c1c2741a75db29736489785a4ba15154ce7ba Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 14:35:25 +0100 Subject: [PATCH 14/27] add excoveralls and dialyxir --- mix.exs | 18 +++++++++++++++++- mix.lock | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 31cd2b9..3719d77 100644 --- a/mix.exs +++ b/mix.exs @@ -15,7 +15,9 @@ defmodule GeoSQL.Mixfile do package: package(), docs: docs(), elixirc_paths: elixirc_paths(Mix.env()), - aliases: aliases() + aliases: aliases(), + preferred_cli_env: cli(), + test_coverage: [tool: ExCoveralls] ] end @@ -39,6 +41,8 @@ defmodule GeoSQL.Mixfile do # dev {:credo, "~> 1.0", only: [:dev, :test]}, + {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, + {:excoveralls, "~> 0.14", only: :test, runtime: false}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, @@ -48,6 +52,18 @@ defmodule GeoSQL.Mixfile do ] end + def cli do + [ + preferred_envs: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.github": :test, + "coveralls.html": :test, + "test.watch": :test + ] + ] + end + defp package do [ description: diff --git a/mix.lock b/mix.lock index 277710d..c01969a 100644 --- a/mix.lock +++ b/mix.lock @@ -4,13 +4,16 @@ "credo": {:hex, :credo, "1.7.16", "a9f1389d13d19c631cb123c77a813dbf16449a2aebf602f590defa08953309d4", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0562af33756b21f248f066a9119e3890722031b6d199f22e3cf95550e4f1579"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, "ecto_sql": {:hex, :ecto_sql, "3.13.4", "b6e9d07557ddba62508a9ce4a484989a5bb5e9a048ae0e695f6d93f095c25d60", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b38cf0749ca4d1c5a8bcbff79bbe15446861ca12a61f9fba604486cb6b62a14"}, "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.22.0", "edab2d0f701b7dd05dcf7e2d97769c106aff62b5cfddc000d1dd6f46b9cbd8c3", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.13.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.22", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "5af9e031bffcc5da0b7bca90c271a7b1e7c04a93fecf7f6cd35bc1b1921a64bd"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, + "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, "exqlite": {:hex, :exqlite, "0.34.0", "ebca3570eb4c4eb4345d76c8e44ce31a62de7b24a54fd118164480f2954bd540", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "bcdc58879a0db5e08cd5f6fbe07a0692ceffaaaa617eab46b506137edf0a2742"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, From 541b5a34d93ba0bfbfe4b64e55eaca29dab044cc Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 14:47:46 +0100 Subject: [PATCH 15/27] myxql-related fixups --- lib/geo_sql.ex | 2 +- lib/geo_sql/mysql/geometry_codec.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/geo_sql.ex b/lib/geo_sql.ex index b902cc5..7b69f87 100644 --- a/lib/geo_sql.ex +++ b/lib/geo_sql.ex @@ -72,7 +72,7 @@ defmodule GeoSQL do end if Code.ensure_loaded?(MyXQL) do - defp register_types(Ecto.Adapters.MyXQL = adapter, repo, opts) do + defp register_types(Ecto.Adapters.MyXQL, _repo, _opts) do Application.put_env(:myxql, :geometry_codec, GeoSQL.MySQL.GeometryCodec) end end diff --git a/lib/geo_sql/mysql/geometry_codec.ex b/lib/geo_sql/mysql/geometry_codec.ex index 50f7d2c..959ab69 100644 --- a/lib/geo_sql/mysql/geometry_codec.ex +++ b/lib/geo_sql/mysql/geometry_codec.ex @@ -1,6 +1,6 @@ if Code.ensure_loaded?(MyXQL) do defmodule GeoSQL.MySQL.GeometryCodec do - @behaviour MyXQL.Protocol.GeometryCodec + @behaviour MyXQL.GeometryCodec supported_structs = [ Geometry.Point, From 5b1691b1dc83850a38f259e1b42a7876662fc789 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 14:48:53 +0100 Subject: [PATCH 16/27] pass with MyXQL --- test/query_utils_test.exs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/query_utils_test.exs b/test/query_utils_test.exs index e210a7f..4a4e0b7 100644 --- a/test/query_utils_test.exs +++ b/test/query_utils_test.exs @@ -39,6 +39,9 @@ defmodule GeoSQL.QueryUtils.Test do Ecto.Adapters.Postgres -> assert(Regex.match?(pattern, query_string)) + Ecto.Adapters.MyXQL -> + refute(Regex.match?(pattern, query_string)) + Ecto.Adapters.SQLite3 -> refute(Regex.match?(pattern, query_string)) end From a694a725767e7fdbd98440ee06a7ba8beea1267c Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 15:22:06 +0100 Subject: [PATCH 17/27] note the ZM support in DBs --- test/support/repo.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/support/repo.ex b/test/support/repo.ex index 6a4bc2b..0425ce8 100644 --- a/test/support/repo.ex +++ b/test/support/repo.ex @@ -3,6 +3,7 @@ defmodule GeoSQL.Test.PostGIS.Repo do def has_array_literals?, do: true def to_boolean(value), do: value + def has_zm?, do: true end defmodule GeoSQL.Test.MySQL.Repo do @@ -12,6 +13,7 @@ defmodule GeoSQL.Test.MySQL.Repo do def to_boolean(1), do: true def to_boolean(0), do: false def to_boolean(value), do: value + def has_zm?, do: false end defmodule GeoSQL.Test.SpatiaLite.Repo do @@ -20,6 +22,7 @@ defmodule GeoSQL.Test.SpatiaLite.Repo do def has_array_literals?, do: false def to_boolean(1), do: true def to_boolean(0), do: false + def has_zm?, do: true end defmodule GeoSQL.Test.Geopackage.Repo do @@ -28,6 +31,7 @@ defmodule GeoSQL.Test.Geopackage.Repo do def has_array_literals?, do: false def to_boolean(1), do: true def to_boolean(0), do: false + def has_zm?, do: true end # For the migrations. From 00a5267d22350ed24e0e230c4c691715bc5dce31 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 15:22:20 +0100 Subject: [PATCH 18/27] remove an unused config line --- config/test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/config/test.exs b/config/test.exs index 7a9a721..1b58fdf 100644 --- a/config/test.exs +++ b/config/test.exs @@ -26,7 +26,6 @@ config :geo_sql, GeoSQL.Test.MySQL.Repo, password: "testing", protocol: :tcp, database: "geosql_tests", - types: GeoSQL.PostgrexTypes, pool: Ecto.Adapters.SQL.Sandbox, priv: "priv/repo/mysql", testing_primary: true, From 6ac89875b6961c0a475af1ec1805b82c7231c816 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 15:22:32 +0100 Subject: [PATCH 19/27] add mysql --- test/test_helper.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index a619e86..6e3f132 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -184,7 +184,7 @@ defmodule GeoSQL.Test.Helper do end end -excludable_tags = [:pgsql, :sqlite3] +excludable_tags = [:pgsql, :sqlite3, :myqsl] exclude_tags = GeoSQL.Test.Helper.repos() @@ -195,6 +195,7 @@ exclude_tags = case repo.__adapter__() do Ecto.Adapters.Postgres -> Enum.reject(exclude_tags, fn tag -> tag == :pgsql end) Ecto.Adapters.SQLite3 -> Enum.reject(exclude_tags, fn tag -> tag == :sqlite3 end) + Ecto.Adapters.MyXQL -> Enum.reject(exclude_tags, fn tag -> tag == :mysql end) _ -> exclude_tags end end From 7117ae1681de9550c8cbace7acc191490eca21c1 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 15:28:10 +0100 Subject: [PATCH 20/27] repo-specific query arg placeholders --- test/support/repo.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/support/repo.ex b/test/support/repo.ex index 0425ce8..53e4905 100644 --- a/test/support/repo.ex +++ b/test/support/repo.ex @@ -4,6 +4,7 @@ defmodule GeoSQL.Test.PostGIS.Repo do def has_array_literals?, do: true def to_boolean(value), do: value def has_zm?, do: true + def arg(n), do: "$#{n}" end defmodule GeoSQL.Test.MySQL.Repo do @@ -14,6 +15,7 @@ defmodule GeoSQL.Test.MySQL.Repo do def to_boolean(0), do: false def to_boolean(value), do: value def has_zm?, do: false + def arg(_n), do: "?" end defmodule GeoSQL.Test.SpatiaLite.Repo do @@ -23,6 +25,7 @@ defmodule GeoSQL.Test.SpatiaLite.Repo do def to_boolean(1), do: true def to_boolean(0), do: false def has_zm?, do: true + def arg(n), do: "$#{n}" end defmodule GeoSQL.Test.Geopackage.Repo do @@ -32,6 +35,7 @@ defmodule GeoSQL.Test.Geopackage.Repo do def to_boolean(1), do: true def to_boolean(0), do: false def has_zm?, do: true + def arg(n), do: "$#{n}" end # For the migrations. From d2d0190e769a45128289bb9d8d133c40f88ee329 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 15:28:21 +0100 Subject: [PATCH 21/27] pass tests with mysql --- test/geo_sql_test.exs | 155 +++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/test/geo_sql_test.exs b/test/geo_sql_test.exs index b61102b..019c4cb 100644 --- a/test/geo_sql_test.exs +++ b/test/geo_sql_test.exs @@ -14,7 +14,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, point) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, point) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [ 42, geo @@ -38,7 +38,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, t, point) VALUES ($1, $2, $3)", + "INSERT INTO specified_columns (id, t, point) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)}, #{unquote(repo).arg(3)})", [ 42, "test", @@ -57,34 +57,13 @@ defmodule GeoSQL.Test do assert(result == [[42, "test", geo]]) end - test "insert pointz (#{repo})" do - geo = %Geometry.PointZ{coordinates: [30, -90, 70], srid: 4326} - - {:ok, _} = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, pointz) VALUES ($1, $2)", - [42, geo] - ) - - result = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "SELECT id, pointz FROM specified_columns", - [] - ) - |> decode(unquote(repo), [1]) - - assert(result == [[42, geo]]) - end - test "insert linestring (#{repo})" do geo = %Geometry.LineString{path: [[30, 10], [10, 30], [40, 40]], srid: 4326} {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, linestring) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, linestring) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [42, geo] ) @@ -99,54 +78,6 @@ defmodule GeoSQL.Test do assert(result == [[42, geo]]) end - test "insert LineStringZ (#{repo})" do - geo = %Geometry.LineStringZ{ - path: [[30, 10, 20], [10, 30, 2], [40, 40, 50]], - srid: 4326 - } - - {:ok, _} = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, linestringz) VALUES ($1, $2)", - [42, geo] - ) - - result = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "SELECT id, linestringz FROM specified_columns", - [] - ) - |> decode(unquote(repo), [1]) - - assert result == [[42, geo]] - end - - test "insert LineStringZM (#{repo})" do - geo = %Geometry.LineStringZM{ - path: [[30, 10, 20, 40], [10, 30, 2, -10], [40, 40, 50, 100]], - srid: 4326 - } - - {:ok, _} = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, linestringzm) VALUES ($1, $2)", - [42, geo] - ) - - result = - Ecto.Adapters.SQL.query( - unquote(repo).get_dynamic_repo(), - "SELECT id, linestringzm FROM specified_columns", - [] - ) - |> decode(unquote(repo), [1]) - - assert result == [[42, geo]] - end - test "insert polygon (#{repo})" do geo = %Geometry.Polygon{ rings: [ @@ -159,7 +90,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, polygon) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, polygon) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [42, geo] ) @@ -180,7 +111,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, multipoint) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, multipoint) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [42, geo] ) @@ -204,7 +135,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, multilinestring) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, multilinestring) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [42, geo] ) @@ -234,7 +165,7 @@ defmodule GeoSQL.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, multipolygon) VALUES ($1, $2)", + "INSERT INTO specified_columns (id, multipolygon) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", [42, geo] ) @@ -248,5 +179,77 @@ defmodule GeoSQL.Test do assert(result == [[42, geo]]) end + + + if repo.has_zm?() do + test "insert pointz (#{repo})" do + geo = %Geometry.PointZ{coordinates: [30, -90, 70], srid: 4326} + + {:ok, _} = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "INSERT INTO specified_columns (id, pointz) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", + [42, geo] + ) + + result = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "SELECT id, pointz FROM specified_columns", + [] + ) + |> decode(unquote(repo), [1]) + + assert(result == [[42, geo]]) + end + + test "insert LineStringZ (#{repo})" do + geo = %Geometry.LineStringZ{ + path: [[30, 10, 20], [10, 30, 2], [40, 40, 50]], + srid: 4326 + } + + {:ok, _} = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "INSERT INTO specified_columns (id, linestringz) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", + [42, geo] + ) + + result = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "SELECT id, linestringz FROM specified_columns", + [] + ) + |> decode(unquote(repo), [1]) + + assert result == [[42, geo]] + end + + test "insert LineStringZM (#{repo})" do + geo = %Geometry.LineStringZM{ + path: [[30, 10, 20, 40], [10, 30, 2, -10], [40, 40, 50, 100]], + srid: 4326 + } + + {:ok, _} = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "INSERT INTO specified_columns (id, linestringzm) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)})", + [42, geo] + ) + + result = + Ecto.Adapters.SQL.query( + unquote(repo).get_dynamic_repo(), + "SELECT id, linestringzm FROM specified_columns", + [] + ) + |> decode(unquote(repo), [1]) + + assert result == [[42, geo]] + end + end end end From 394cc9597c367b61cfa361d23199c6abfc0f75e8 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 16:03:54 +0100 Subject: [PATCH 22/27] typo --- test/test_helper.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index 6e3f132..9197860 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -184,7 +184,7 @@ defmodule GeoSQL.Test.Helper do end end -excludable_tags = [:pgsql, :sqlite3, :myqsl] +excludable_tags = [:pgsql, :sqlite3, :mysql] exclude_tags = GeoSQL.Test.Helper.repos() From 2522422e2e64cd25d313ca8e0f6c51fab11ef95c Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 16:04:02 +0100 Subject: [PATCH 23/27] pass with MySQL --- test/common_functions_test.exs | 142 +++++++++++++++++---------------- 1 file changed, 72 insertions(+), 70 deletions(-) diff --git a/test/common_functions_test.exs b/test/common_functions_test.exs index 9d449bb..b7fc55e 100644 --- a/test/common_functions_test.exs +++ b/test/common_functions_test.exs @@ -14,6 +14,78 @@ defmodule GeoSQL.CommonFunctions.Test do alias GeoSQL.Test.Schema.{Location, LocationMulti, GeoType} for repo <- Helper.repos() do + describe "Common: as_geojson (#{repo})" do + test "returns correct binary data" do + geom = Fixtures.multipolygon() + unquote(repo).insert(%Location{name: "hello", geom: geom}) + + query = + from(location in Location, select: Common.as_geojson(location.geom, unquote(repo))) + + [geojson] = unquote(repo).all(query) + + hydrated = + geojson + |> Jason.decode!() + |> Geometry.from_geo_json!() + + assert 4326 = hydrated.srid + assert Helper.fuzzy_match_geometry(geom.polygons, hydrated.polygons) + end + end + + describe "Common: degrees (#{repo})" do + test "Converts radians to degrees" do + geom = Fixtures.point() + unquote(repo).insert(%Location{name: "hello", geom: geom}) + query = from(location in Location, select: Common.degrees(0.5)) + result = unquote(repo).one(query) + assert is_number(result) + end + end + + describe "Common: geom_from_geojson (#{repo})" do + test "returns a geometry" do + point = Fixtures.point() + + geojson = + Geometry.to_geo_json(point) + |> :json.encode() + |> to_string() + + unquote(repo).insert(%GeoType{t: geojson, point: point}) + + result = + from(g in GeoType, select: Common.geom_from_geojson(g.t, unquote(repo))) + |> unquote(repo).one() + |> QueryUtils.decode_geometry(unquote(repo)) + + assert Helper.fuzzy_match_geometry(result.coordinates, point.coordinates) + end + end + + describe "Common: number_of_geometries (#{repo})" do + test "Returns correct number of rings" do + geom = Fixtures.geometry_collection() + unquote(repo).insert(%Location{name: "hello", geom: geom}) + query = from(location in Location, select: Common.number_of_geometries(location.geom)) + result = unquote(repo).one(query) + assert 2 == result + end + end + + describe "Common: radians (#{repo})" do + test "Converts degrees to radians" do + geom = Fixtures.point() + unquote(repo).insert(%Location{name: "hello", geom: geom}) + query = from(location in Location, select: Common.radians(45)) + result = unquote(repo).one(query) + assert is_number(result) + end + end + end + + for repo <- (Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo)) do describe "Common: add_measure (#{repo})" do test "adds measure values to a line" do line = Fixtures.linestring() @@ -110,26 +182,6 @@ defmodule GeoSQL.CommonFunctions.Test do end end - describe "Common: as_geojson (#{repo})" do - test "returns correct binary data" do - geom = Fixtures.multipolygon() - unquote(repo).insert(%Location{name: "hello", geom: geom}) - - query = - from(location in Location, select: Common.as_geojson(location.geom, unquote(repo))) - - [geojson] = unquote(repo).all(query) - - hydrated = - geojson - |> Jason.decode!() - |> Geometry.from_geo_json!() - - assert 4326 = hydrated.srid - assert Helper.fuzzy_match_geometry(geom.polygons, hydrated.polygons) - end - end - describe "Common: as_gml (#{repo})" do test "returns correct v3 gml" do geom = Fixtures.multipolygon() @@ -503,16 +555,6 @@ defmodule GeoSQL.CommonFunctions.Test do end end - describe "Common: degrees (#{repo})" do - test "Converts radians to degrees" do - geom = Fixtures.point() - unquote(repo).insert(%Location{name: "hello", geom: geom}) - query = from(location in Location, select: Common.degrees(0.5)) - result = unquote(repo).one(query) - assert is_number(result) - end - end - describe "Common: estimated_extent (#{repo})" do test "Finds the estimated extent of a table" do geom = Fixtures.polygon() @@ -647,26 +689,6 @@ defmodule GeoSQL.CommonFunctions.Test do end end - describe "Common: geom_from_geojson (#{repo})" do - test "returns a geometry" do - point = Fixtures.point() - - geojson = - Geometry.to_geo_json(point) - |> :json.encode() - |> to_string() - - unquote(repo).insert(%GeoType{t: geojson, point: point}) - - result = - from(g in GeoType, select: Common.geom_from_geojson(g.t, unquote(repo))) - |> unquote(repo).one() - |> QueryUtils.decode_geometry(unquote(repo)) - - assert Helper.fuzzy_match_geometry(result.coordinates, point.coordinates) - end - end - describe "Common: geom_from_kml (#{repo})" do test "untested" do point = Fixtures.point() @@ -1372,16 +1394,6 @@ defmodule GeoSQL.CommonFunctions.Test do end end - describe "Common: number_of_geometries (#{repo})" do - test "Returns correct number of rings" do - geom = Fixtures.geometry_collection() - unquote(repo).insert(%Location{name: "hello", geom: geom}) - query = from(location in Location, select: Common.number_of_geometries(location.geom)) - result = unquote(repo).one(query) - assert 2 == result - end - end - describe "Common: oriented_envelope (#{repo})" do test "Returns correct number of rings" do geom = Fixtures.geometry_collection() @@ -1425,16 +1437,6 @@ defmodule GeoSQL.CommonFunctions.Test do end end - describe "Common: radians (#{repo})" do - test "Converts degrees to radians" do - geom = Fixtures.point() - unquote(repo).insert(%Location{name: "hello", geom: geom}) - query = from(location in Location, select: Common.radians(45)) - result = unquote(repo).one(query) - assert is_number(result) - end - end - describe "Common: reduce_precision (#{repo})" do test "returns a geometry" do geom = Fixtures.point() From 0c0833db09f3ccf35689870b9b43cae7e593c522 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 20:37:21 +0100 Subject: [PATCH 24/27] get tests to pass with mysql. lots of exclusions. *sigh* --- test/ecto_test.exs | 2 +- test/mm_functions_test.exs | 2 +- test/test_helper.exs | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/ecto_test.exs b/test/ecto_test.exs index 7cc6ac8..029b2a9 100644 --- a/test/ecto_test.exs +++ b/test/ecto_test.exs @@ -66,7 +66,7 @@ defmodule GeoSQL.Ecto.Test do {:ok, _} = Ecto.Adapters.SQL.query( unquote(repo).get_dynamic_repo(), - "INSERT INTO specified_columns (id, t, point, linestring) VALUES ($1, $2, $3, $4)", + "INSERT INTO specified_columns (id, t, point, linestring) VALUES (#{unquote(repo).arg(1)}, #{unquote(repo).arg(2)}, #{unquote(repo).arg(3)}, #{unquote(repo).arg(4)})", [ 42, "test", diff --git a/test/mm_functions_test.exs b/test/mm_functions_test.exs index 7bd7038..2f5d30f 100644 --- a/test/mm_functions_test.exs +++ b/test/mm_functions_test.exs @@ -31,7 +31,7 @@ defmodule GeoSQL.MMFunctions.Test do end end - for repo <- Helper.repos() do + for repo <- (Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo)) do describe "SQL/MM: area (#{repo})" do test "returns a numeric result" do query = from(location in Location, select: MM.area(location.geom)) diff --git a/test/test_helper.exs b/test/test_helper.exs index 9197860..48bfbd2 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -145,9 +145,7 @@ defmodule GeoSQL.Test.Helper do # horrible hack here, but we need to start repo supervised for setup_all functions # that need access to a global db object they can modify for all tests aftewards. - pid = - ExUnit.Callbacks.start_link_supervised!(repo_spec) - |> IO.inspect(label: repo) + pid = ExUnit.Callbacks.start_link_supervised!(repo_spec) Ecto.Adapters.SQL.Sandbox.mode(pid, :manual) From 2cff816655d7f5d5b7eab7495c63d7ba7345c391 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 20:48:20 +0100 Subject: [PATCH 25/27] credo fixes --- lib/geo_sql.ex | 10 ++-- lib/geo_sql/ecto_types/geometry.ex | 70 ++++++++++++------------ lib/geo_sql/mysql/geometry_codec.ex | 1 + lib/geo_sql/query_utils.ex | 4 +- lib/geo_sql/spatialite/type_extension.ex | 6 +- test/common_functions_test.exs | 4 +- test/ecto_test.exs | 4 +- test/geo_sql_test.exs | 1 - test/mm_functions_test.exs | 4 +- test/query_utils_test.exs | 2 +- test/test_helper.exs | 6 +- 11 files changed, 53 insertions(+), 59 deletions(-) diff --git a/lib/geo_sql.ex b/lib/geo_sql.ex index 7b69f87..96d67b4 100644 --- a/lib/geo_sql.ex +++ b/lib/geo_sql.ex @@ -80,11 +80,9 @@ defmodule GeoSQL do defp register_types(adapter, _repo, _opts), do: adapter defp run_query(repo, sql) do - try do - Ecto.Adapters.SQL.query!(repo.get_dynamic_repo(), sql) - :ok - rescue - _ -> :error - end + Ecto.Adapters.SQL.query!(repo.get_dynamic_repo(), sql) + :ok + rescue + _ -> :error end end diff --git a/lib/geo_sql/ecto_types/geometry.ex b/lib/geo_sql/ecto_types/geometry.ex index fd08035..76e89b6 100644 --- a/lib/geo_sql/ecto_types/geometry.ex +++ b/lib/geo_sql/ecto_types/geometry.ex @@ -37,34 +37,34 @@ defmodule GeoSQL.Geometry do """ alias Geometry.{ - Point, - PointZ, - PointM, - PointZM, + GeometryCollection, + GeometryCollectionM, + GeometryCollectionZ, + GeometryCollectionZM, LineString, - LineStringZ, LineStringM, + LineStringZ, LineStringZM, - Polygon, - PolygonZ, - PolygonM, - PolygonZM, - MultiPoint, - MultiPointZ, - MultiPointM, - MultiPointZM, MultiLineString, - MultiLineStringZ, MultiLineStringM, + MultiLineStringZ, MultiLineStringZM, + MultiPoint, + MultiPointM, + MultiPointZ, + MultiPointZM, MultiPolygon, - MultiPolygonZ, MultiPolygonM, + MultiPolygonZ, MultiPolygonZM, - GeometryCollection, - GeometryCollectionZ, - GeometryCollectionM, - GeometryCollectionZM + Point, + PointM, + PointZ, + PointZM, + Polygon, + PolygonM, + PolygonZ, + PolygonZM } @types [ @@ -233,14 +233,12 @@ defmodule GeoSQL.Geometry do end defp do_cast(geom) when is_binary(geom) do - try do - geom - |> JSON.decode!() - |> do_cast() - rescue - error in ErlangError -> - {:error, [message: "Failed to decode JSON", reason: error.original]} - end + geom + |> JSON.decode!() + |> do_cast() + rescue + error in ErlangError -> + {:error, [message: "Failed to decode JSON", reason: error.original]} end defp do_cast(geom) do @@ -325,24 +323,24 @@ if Code.ensure_loaded?(Geo) and not Code.ensure_loaded?(Geo.PostGIS.Geometry) do @moduledoc false alias Geo.{ - Point, - PointZ, - PointM, - PointZM, + GeometryCollection, LineString, LineStringM, LineStringZ, LineStringZM, - Polygon, - PolygonZ, - MultiPoint, - MultiPointZ, MultiLineString, MultiLineStringZ, MultiLineStringZM, + MultiPoint, + MultiPointZ, MultiPolygon, MultiPolygonZ, - GeometryCollection + Point, + PointM, + PointZ, + PointZM, + Polygon, + PolygonZ } @types [ diff --git a/lib/geo_sql/mysql/geometry_codec.ex b/lib/geo_sql/mysql/geometry_codec.ex index 959ab69..ff6911a 100644 --- a/lib/geo_sql/mysql/geometry_codec.ex +++ b/lib/geo_sql/mysql/geometry_codec.ex @@ -1,5 +1,6 @@ if Code.ensure_loaded?(MyXQL) do defmodule GeoSQL.MySQL.GeometryCodec do + @moduledoc false @behaviour MyXQL.GeometryCodec supported_structs = [ diff --git a/lib/geo_sql/query_utils.ex b/lib/geo_sql/query_utils.ex index ceec8b8..b8b9e75 100644 --- a/lib/geo_sql/query_utils.ex +++ b/lib/geo_sql/query_utils.ex @@ -255,17 +255,15 @@ defmodule GeoSQL.QueryUtils do defp decode_all(query_result, _, _), do: query_result defp decode_one(type_extension, encoded_field) do - try do case type_extension.decode_geometry(encoded_field) do {:ok, successfully_decoded_field} -> successfully_decoded_field :error -> encoded_field {:error, _} -> encoded_field end - rescue + rescue _ -> # error, usually because the field was not a geometry. # swallow that error encoded_field - end end end diff --git a/lib/geo_sql/spatialite/type_extension.ex b/lib/geo_sql/spatialite/type_extension.ex index f527c82..daf9a27 100644 --- a/lib/geo_sql/spatialite/type_extension.ex +++ b/lib/geo_sql/spatialite/type_extension.ex @@ -72,15 +72,15 @@ defmodule GeoSQL.SpatiaLite.TypeExtension do @moduledoc false use Agent - def start_link() do + def start_link do Agent.start_link(&start_conn/0, name: __MODULE__) end - def start() do + def start do Agent.start(&start_conn/0, name: __MODULE__) end - def start_conn() do + def start_conn do {:ok, conn} = Exqlite.Sqlite3.open(":memory:") Exqlite.Sqlite3.enable_load_extension(conn, true) Exqlite.Sqlite3.execute(conn, "select load_extension('mod_spatialite')") diff --git a/test/common_functions_test.exs b/test/common_functions_test.exs index b7fc55e..6391e83 100644 --- a/test/common_functions_test.exs +++ b/test/common_functions_test.exs @@ -11,7 +11,7 @@ defmodule GeoSQL.CommonFunctions.Test do use GeoSQL.Test.Helper alias GeoSQL.Test.Helper - alias GeoSQL.Test.Schema.{Location, LocationMulti, GeoType} + alias GeoSQL.Test.Schema.{GeoType, Location, LocationMulti} for repo <- Helper.repos() do describe "Common: as_geojson (#{repo})" do @@ -85,7 +85,7 @@ defmodule GeoSQL.CommonFunctions.Test do end end - for repo <- (Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo)) do + for repo <- Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo) do describe "Common: add_measure (#{repo})" do test "adds measure values to a line" do line = Fixtures.linestring() diff --git a/test/ecto_test.exs b/test/ecto_test.exs index 029b2a9..aff5306 100644 --- a/test/ecto_test.exs +++ b/test/ecto_test.exs @@ -6,11 +6,11 @@ defmodule GeoSQL.Ecto.Test do use GeoSQL.Test.Helper alias GeoSQL.Test.Schema.{ - Location, GeoCompatLocation, Geographies, - LocationMulti, GeoType, + Location, + LocationMulti, WrongGeoType } diff --git a/test/geo_sql_test.exs b/test/geo_sql_test.exs index 019c4cb..7c9f784 100644 --- a/test/geo_sql_test.exs +++ b/test/geo_sql_test.exs @@ -180,7 +180,6 @@ defmodule GeoSQL.Test do assert(result == [[42, geo]]) end - if repo.has_zm?() do test "insert pointz (#{repo})" do geo = %Geometry.PointZ{coordinates: [30, -90, 70], srid: 4326} diff --git a/test/mm_functions_test.exs b/test/mm_functions_test.exs index 2f5d30f..1ac7a65 100644 --- a/test/mm_functions_test.exs +++ b/test/mm_functions_test.exs @@ -9,7 +9,7 @@ defmodule GeoSQL.MMFunctions.Test do use GeoSQL.QueryUtils use GeoSQL.Common - alias GeoSQL.Test.Schema.{Location, LocationMulti, GeoType, Geographies} + alias GeoSQL.Test.Schema.{Geographies, GeoType, Location, LocationMulti} setup do geom = Fixtures.multipolygon() @@ -31,7 +31,7 @@ defmodule GeoSQL.MMFunctions.Test do end end - for repo <- (Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo)) do + for repo <- Helper.repos() |> List.delete(GeoSQL.Test.MySQL.Repo) do describe "SQL/MM: area (#{repo})" do test "returns a numeric result" do query = from(location in Location, select: MM.area(location.geom)) diff --git a/test/query_utils_test.exs b/test/query_utils_test.exs index 4a4e0b7..2d7bd53 100644 --- a/test/query_utils_test.exs +++ b/test/query_utils_test.exs @@ -7,7 +7,7 @@ defmodule GeoSQL.QueryUtils.Test do use GeoSQL.RepoUtils use GeoSQL.Common use GeoSQL.Test.Helper - alias GeoSQL.Test.Schema.{GeoType} + alias GeoSQL.Test.Schema.GeoType encoding_repos = [Ecto.Adapters.SQLite3] diff --git a/test/test_helper.exs b/test/test_helper.exs index 48bfbd2..ae3db08 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -97,12 +97,12 @@ defmodule GeoSQL.Test.Helper do setup_funs = Keyword.get(options, :setup_funs, []) quote do + alias GeoSQL.Test.Fixtures + alias GeoSQL.Test.Geopackage.Repo, as: GeopackageRepo + alias GeoSQL.Test.Helper alias GeoSQL.Test.MySQL.Repo, as: MySQLRepo alias GeoSQL.Test.PostGIS.Repo, as: PostGISRepo alias GeoSQL.Test.SpatiaLite.Repo, as: SpatialiteRepo - alias GeoSQL.Test.Geopackage.Repo, as: GeopackageRepo - alias GeoSQL.Test.Fixtures - alias GeoSQL.Test.Helper setup_all [{GeoSQL.Test.Helper, :ecto_setup_all}] ++ unquote(setup_funs) From 5d9cc8fdc119a8543e0e4154b88739c16a51dc62 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 20:52:07 +0100 Subject: [PATCH 26/27] eh, skip CI. for these db workloads and weird macro src, it is not helping much --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b93203..0f17c95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,8 +62,8 @@ jobs: -${{ steps.setup-beam.outputs.setup-beam-version }}" - name: Get dependencies run: mix deps.get - - name: Compile dependencies - run: MIX_ENV=test mix deps.compile + # - name: Compile dependencies + # run: MIX_ENV=test mix deps.compile - name: Compile project run: MIX_ENV=test mix compile --warnings-as-errors - name: Check unused dependencies From 215a248ea4d2d396ccc14b2e53df8fbac40a5d51 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 11 Mar 2026 20:56:09 +0100 Subject: [PATCH 27/27] eh, skip CI. for these db workloads and weird macro src, it is not helping much --- .github/workflows/ci.yml | 86 ---------------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0f17c95..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,86 +0,0 @@ -# Created with GitHubActions version 0.3.9 -name: CI -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -on: - - pull_request -jobs: - linux: - name: Test on Ubuntu (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }}) - runs-on: ubuntu-24.04 - strategy: - matrix: - include: - - elixir: '1.19.1' - otp: '28.1' - coverage: true - - elixir: '1.19.1' - otp: '27.3' - - elixir: '1.19.1' - otp: '26.2' - - elixir: '1.18.4' - otp: '28.1' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup Elixir - id: setup-beam - uses: erlef/setup-beam@v1 - with: - elixir-version: ${{ matrix.elixir }} - otp-version: ${{ matrix.otp }} - - name: Restore deps - uses: actions/cache@v4 - with: - path: deps - key: "deps\ - -${{ runner.os }}\ - -${{ matrix.elixir }}\ - -${{ matrix.otp }}\ - -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ - -${{ steps.setup-beam.outputs.setup-beam-version }}" - - name: Restore _build - uses: actions/cache@v4 - with: - path: _build - key: "_build\ - -${{ runner.os }}\ - -${{ matrix.elixir }}\ - -${{ matrix.otp }}\ - -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ - -${{ steps.setup-beam.outputs.setup-beam-version }}" - - name: Restore test/support/plts - if: ${{ matrix.lint }} - uses: actions/cache@v4 - with: - path: test/support/plts - key: "test/support/plts\ - -${{ runner.os }}\ - -${{ matrix.elixir }}\ - -${{ matrix.otp }}\ - -${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}\ - -${{ steps.setup-beam.outputs.setup-beam-version }}" - - name: Get dependencies - run: mix deps.get - # - name: Compile dependencies - # run: MIX_ENV=test mix deps.compile - - name: Compile project - run: MIX_ENV=test mix compile --warnings-as-errors - - name: Check unused dependencies - if: ${{ matrix.lint }} - run: mix deps.unlock --check-unused - - name: Check code format - if: ${{ matrix.lint }} - run: mix format --check-formatted - - name: Lint code - if: ${{ matrix.lint }} - run: mix credo --strict - - name: Run tests - if: ${{ !matrix.coverage }} - run: mix test - - name: Run tests with coverage - if: ${{ matrix.coverage }} - run: mix coveralls.github - - name: Static code analysis - if: ${{ matrix.lint }} - run: mix dialyzer --format github --force-check