From cb9593b37143f09887ad61bd9ece39f75f8aa86f Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Fri, 20 Jun 2025 13:47:40 +0200 Subject: [PATCH 1/3] Exqlite.TypeExtensions Runtime extensions to convert data types into something that can be serialized into the database. --- README.md | 9 +++++++-- lib/exqlite/extension.ex | 11 +++++++++++ lib/exqlite/sqlite3.ex | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 lib/exqlite/extension.ex diff --git a/README.md b/README.md index 5de68e5..b667cb9 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,17 @@ end ### Runtime Configuration ```elixir -config :exqlite, default_chunk_size: 100 +config :exqlite, + default_chunk_size: 100, + type_extensions: [MyApp.TypeExtension] ``` * `default_chunk_size` - The chunk size that is used when multi-stepping when not specifying the chunk size explicitly. - +* `type_extensions`: An optional list of modules that implement the + Exqlite.TypeExtension behaviour, allowing types beyond the default set that + can be stored and retrieved from the database. + ### Compile-time Configuration In `config/config.exs`, diff --git a/lib/exqlite/extension.ex b/lib/exqlite/extension.ex new file mode 100644 index 0000000..badab5a --- /dev/null +++ b/lib/exqlite/extension.ex @@ -0,0 +1,11 @@ +defmodule Exqlite.TypeExtension do + @moduledoc """ + A behaviour that defines the API for extensions providing custom data loaders and dumpers + for Ecto schemas. + """ + + @doc """ + Takes a value and convers it to data suitable for storage in the database. + """ + @callback convert(value :: term) :: term +end diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index 64acd12..fd3bba1 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -481,5 +481,20 @@ defmodule Exqlite.Sqlite3 do raise ArgumentError, "#{inspect(datetime)} is not in UTC" end - defp convert(val), do: val + defp convert(val) do + convert_with_type_extensions(type_extensions(), val) + end + + defp convert_with_type_extensions([], val), do: val + + defp convert_with_type_extensions([extension | other_extensions], val) do + case extension.convert(val) do + nil -> convert_with_type_extensions(other_extensions, val) + convert -> convert + end + end + + defp type_extensions() do + Application.get_env(:exqlite, :type_extensions, []) + end end From 7b5d2ee486520b2d110cd63fb56303a342a49c3b Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Fri, 20 Jun 2025 15:17:39 +0200 Subject: [PATCH 2/3] Use a tagged :ok/:error tuple --- lib/exqlite/sqlite3.ex | 13 ++++++++++--- lib/exqlite/{extension.ex => type_extension.ex} | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) rename lib/exqlite/{extension.ex => type_extension.ex} (58%) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index fd3bba1..a641b13 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -489,12 +489,19 @@ defmodule Exqlite.Sqlite3 do defp convert_with_type_extensions([extension | other_extensions], val) do case extension.convert(val) do - nil -> convert_with_type_extensions(other_extensions, val) - convert -> convert + nil -> + convert_with_type_extensions(other_extensions, val) + + {:ok, converted} -> + converted + + {:error, reason} -> + raise ArgumentError, + "Failed conversion by TypeExtension #{extension}: #{inspect(val)}. Reason: #{inspect(reason)}." end end - defp type_extensions() do + defp type_extensions do Application.get_env(:exqlite, :type_extensions, []) end end diff --git a/lib/exqlite/extension.ex b/lib/exqlite/type_extension.ex similarity index 58% rename from lib/exqlite/extension.ex rename to lib/exqlite/type_extension.ex index badab5a..09e9d12 100644 --- a/lib/exqlite/extension.ex +++ b/lib/exqlite/type_extension.ex @@ -6,6 +6,9 @@ defmodule Exqlite.TypeExtension do @doc """ Takes a value and convers it to data suitable for storage in the database. + + Returns a tagged :ok/:error tuple. If the value is not convertable by this + extension, returns nil. """ - @callback convert(value :: term) :: term + @callback convert(value :: term) :: {:ok, term} | {:error, reason :: term} | nil end From 704c9bdf9e89dbab357d265e30a4972597cbdbdf Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Fri, 20 Jun 2025 15:22:28 +0200 Subject: [PATCH 3/3] default to nil --- lib/exqlite/sqlite3.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/exqlite/sqlite3.ex b/lib/exqlite/sqlite3.ex index a641b13..906b6c4 100644 --- a/lib/exqlite/sqlite3.ex +++ b/lib/exqlite/sqlite3.ex @@ -485,6 +485,7 @@ defmodule Exqlite.Sqlite3 do convert_with_type_extensions(type_extensions(), val) end + defp convert_with_type_extensions(nil, val), do: val defp convert_with_type_extensions([], val), do: val defp convert_with_type_extensions([extension | other_extensions], val) do @@ -502,6 +503,6 @@ defmodule Exqlite.Sqlite3 do end defp type_extensions do - Application.get_env(:exqlite, :type_extensions, []) + Application.get_env(:exqlite, :type_extensions) end end