Skip to content

Commit 6fe9100

Browse files
authored
Add Exqlite.TypeExtensions to allow more types to be stored in the database (#333)
Introduces a runtime extension to convert data types into something that can be serialized into the database. See elixir-sqlite/ecto_sqlite3#167 and elixir-sqlite/ecto_sqlite3#166 My primary goal is to be able to use Spatialite from Elixir. With this PR and the one against `ecto_sqlite3`, it is 95% of the way there. With these two PRs applied, one can now do things like: ```elixir defmodule Location do use Ecto.Schema schema "locations" do field(:name, :string) field(:geom, GeoSQL.Geometry) end end MyApp.Repo.all(from(location in Location, select: MM2.distance(location.geom, ^geometry))) ``` .. and it just works, regardless of whether `MyApp.Repo` is point at a PostgreSQL database with PostGIS enabled, or an SQLite3 database with Spatialite loaded. The one remaining annoyance is getting structs back *out* of the database without going through ecto (which *does* work with the `ecto_sqlite3` PR!). So, I still have to figure out a nice way for the user to get back `Geo` structs from raw queries such as: ``` from(location in Location, limit: 1, select: MM2.transform(location.geom, 3452)) ``` But otherwise everything Just Works transparently with this change.
1 parent b8035f2 commit 6fe9100

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,17 @@ end
4646
### Runtime Configuration
4747

4848
```elixir
49-
config :exqlite, default_chunk_size: 100
49+
config :exqlite,
50+
default_chunk_size: 100,
51+
type_extensions: [MyApp.TypeExtension]
5052
```
5153

5254
* `default_chunk_size` - The chunk size that is used when multi-stepping when
5355
not specifying the chunk size explicitly.
54-
56+
* `type_extensions`: An optional list of modules that implement the
57+
Exqlite.TypeExtension behaviour, allowing types beyond the default set that
58+
can be stored and retrieved from the database.
59+
5560
### Compile-time Configuration
5661

5762
In `config/config.exs`,

lib/exqlite/sqlite3.ex

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,5 +481,28 @@ defmodule Exqlite.Sqlite3 do
481481
raise ArgumentError, "#{inspect(datetime)} is not in UTC"
482482
end
483483

484-
defp convert(val), do: val
484+
defp convert(val) do
485+
convert_with_type_extensions(type_extensions(), val)
486+
end
487+
488+
defp convert_with_type_extensions(nil, val), do: val
489+
defp convert_with_type_extensions([], val), do: val
490+
491+
defp convert_with_type_extensions([extension | other_extensions], val) do
492+
case extension.convert(val) do
493+
nil ->
494+
convert_with_type_extensions(other_extensions, val)
495+
496+
{:ok, converted} ->
497+
converted
498+
499+
{:error, reason} ->
500+
raise ArgumentError,
501+
"Failed conversion by TypeExtension #{extension}: #{inspect(val)}. Reason: #{inspect(reason)}."
502+
end
503+
end
504+
505+
defp type_extensions do
506+
Application.get_env(:exqlite, :type_extensions)
507+
end
485508
end

lib/exqlite/type_extension.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Exqlite.TypeExtension do
2+
@moduledoc """
3+
A behaviour that defines the API for extensions providing custom data loaders and dumpers
4+
for Ecto schemas.
5+
"""
6+
7+
@doc """
8+
Takes a value and convers it to data suitable for storage in the database.
9+
10+
Returns a tagged :ok/:error tuple. If the value is not convertable by this
11+
extension, returns nil.
12+
"""
13+
@callback convert(value :: term) :: {:ok, term} | {:error, reason :: term} | nil
14+
end

0 commit comments

Comments
 (0)