Skip to content

introduce Ecto.Adapters.SQLite3.Extension #167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ config :my_app, MyApp.Repo,
database: "path/to/my/database.db"
```

## Type Extensions

Type extensions allow custom data types to be stored and retrieved from an SQLite3 database.

This is done by implementing a module with the `Ecto.Adapters.SQLite3.TypeExtension` behaviour which maps types to encoder and decoder functions. Type extensions are activated by adding them to the `ecto_sqlite3` configuration as a list of type extention modules assigned to the `type_extensions` key:

```elixir
config :exqlite:
type_extensions: [MyApp.TypeExtension]

config :ecto_sqlite3,
type_extensions: [MyApp.TypeExtension]
```

## Database Encryption

As of version 0.9, `exqlite` supports loading database engines at runtime rather than compiling `sqlite3.c` itself.
Expand Down
38 changes: 34 additions & 4 deletions lib/ecto/adapters/sqlite3.ex
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ defmodule Ecto.Adapters.SQLite3 do
end

@impl Ecto.Adapter
def loaders(_, type) do
[type]
def loaders(primitive_type, ecto_type) do
loader_from_extension(primitive_type, ecto_type)
end

##
Expand Down Expand Up @@ -528,8 +528,8 @@ defmodule Ecto.Adapters.SQLite3 do
end

@impl Ecto.Adapter
def dumpers(_primitive, type) do
[type]
def dumpers(primitive_type, ecto_type) do
dumper_from_extension(primitive_type, ecto_type)
end

##
Expand Down Expand Up @@ -569,4 +569,34 @@ defmodule Ecto.Adapters.SQLite3 do

System.cmd(cmd, args, cmd_opts)
end

defp extensions do
Application.get_env(:ecto_sqlite3, :type_extensions, [])
end

defp loader_from_extension(primitive_type, ecto_type) do
loader_from_extension(extensions(), primitive_type, ecto_type)
end

defp loader_from_extension([], _primitive_type, ecto_type), do: [ecto_type]

defp loader_from_extension([extension | other_extensions], primitive_type, ecto_type) do
case extension.loaders(primitive_type, ecto_type) do
nil -> loader_from_extension(other_extensions, primitive_type, ecto_type)
loader -> loader
end
end

defp dumper_from_extension(primitive_type, ecto_type) do
dumper_from_extension(extensions(), primitive_type, ecto_type)
end

defp dumper_from_extension([], _primitive_type, ecto_type), do: [ecto_type]

defp dumper_from_extension([extension | other_extensions], primitive_type, ecto_type) do
case extension.dumpers(primitive_type, ecto_type) do
nil -> dumper_from_extension(other_extensions, primitive_type, ecto_type)
dumper -> dumper
end
end
end
31 changes: 31 additions & 0 deletions lib/ecto/adapters/sqlite3/type_extension.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule Ecto.Adapters.SQLite3.TypeExtension do
@moduledoc """
A behaviour that defines the API for extensions providing custom data loaders and dumpers
for Ecto schemas.
"""

@type primitive_type :: atom | {:map, type :: atom}
@type ecto_type :: atom

@doc """
Takes a primitive type and, if it knows how to decode it into an
appropriate Elixir data structure, reutrns a two-element list in
the form `[(db_data :: any -> term), elixir_type :: atom]`.

The function that is the first element will be called whenever
the `primitive_type` appears in a schema and data is fetched from
the database for it.
"""
@callback loaders(primitive_type, ecto_type) :: list | nil

@doc """
Takes a primitive type and, if it knows how to encode it for storage
in the database, returns a two-element list in
the form `[elixir_type :: atom, (term -> db_data :: any)]`.

The function that is the second element will be called whenever
the `primitive_type` appears in a schema and data is about to be
sent to the database for storage.
"""
@callback dumpers(ecto_type, primitive_type) :: list | nil
end
Loading