Packages
exqlite
0.4.8
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.1
0.30.0
0.29.0
0.28.0
0.27.1
0.27.0
0.26.0
0.25.0
0.24.2
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.15
0.13.14
0.13.13
0.13.12
0.13.11
0.13.10
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.0
0.11.9
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
An Elixir SQLite3 library
Current section
Files
Jump to
Current section
Files
lib/ecto/adapters/exqlite.ex
defmodule Ecto.Adapters.Exqlite do
use Ecto.Adapters.SQL,
driver: :exqlite
@behaviour Ecto.Adapter.Storage
@behaviour Ecto.Adapter.Structure
alias Ecto.Adapters.Exqlite.Codec
@impl Ecto.Adapter.Storage
def storage_down(options) do
db_path = Keyword.fetch!(options, :database)
with :ok <- File.rm(db_path) do
File.rm(db_path <> "-shm")
File.rm(db_path <> "-wal")
:ok
else
_ -> {:error, :already_down}
end
end
@impl Ecto.Adapter.Storage
def storage_status(options) do
db_path = Keyword.fetch!(options, :database)
if File.exists?(db_path) do
:up
else
:down
end
end
@impl Ecto.Adapter.Storage
def storage_up(options) do
options
|> Keyword.get(:database)
|> storage_up_with_path()
end
@impl Ecto.Adapter.Migration
def supports_ddl_transaction?(), do: false
@impl Ecto.Adapter.Migration
def lock_for_migrations(_meta, query, _options, fun) do
fun.(query)
end
@impl Ecto.Adapter.Structure
def structure_dump(default, config) do
path = config[:dump_path] || Path.join(default, "structure.sql")
with {:ok, contents} <- dump_schema(config),
{:ok, versions} <- dump_versions(config) do
File.mkdir_p!(Path.dirname(path))
File.write!(path, contents <> versions)
{:ok, path}
else
err -> err
end
end
@impl Ecto.Adapter.Structure
def structure_load(default, config) do
path = config[:dump_path] || Path.join(default, "structure.sql")
case run_with_cmd("sqlite3", [config[:database], ".read #{path}"]) do
{_output, 0} -> {:ok, path}
{output, _} -> {:error, output}
end
end
##
## Loaders
##
@impl Ecto.Adapter
def loaders(:boolean, type) do
[&Codec.bool_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:naive_datetime_usec, type) do
[&Codec.naive_datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:utc_datetime_usec, type) do
[&Codec.datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:utc_datetime, type) do
[&Codec.datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:naive_datetime, type) do
[&Codec.naive_datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:datetime, type) do
[&Codec.datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:date, type) do
[&Codec.date_decode/1, type]
end
@impl Ecto.Adapter
def loaders({:map, _}, type) do
[&Codec.json_decode/1, &Ecto.Type.embedded_load(type, &1, :json)]
end
@impl Ecto.Adapter
def loaders({:array, _}, type) do
[&Codec.json_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:map, type) do
[&Codec.json_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:float, type) do
[&Codec.float_decode/1, type]
end
@impl Ecto.Adapter
def loaders(:decimal, type) do
[&Codec.decimal_decode/1, type]
end
# when we have an e.g., max(created_date) function
# Ecto does not truly know the return type, hence :maybe
# see Ecto.Query.Planner.collect_fields
@impl Ecto.Adapter
def loaders({:maybe, :naive_datetime}, type) do
[&Codec.naive_datetime_decode/1, type]
end
@impl Ecto.Adapter
def loaders(_, type) do
[type]
end
##
## Dumpers
##
@impl Ecto.Adapter
def dumpers(:binary, type) do
[type, &Codec.blob_encode/1]
end
@impl Ecto.Adapter
def dumpers(:boolean, type) do
[type, &Codec.bool_encode/1]
end
@impl Ecto.Adapter
def dumpers(:decimal, type) do
[type, &Codec.decimal_encode/1]
end
@impl Ecto.Adapter
def dumpers(:time, type) do
[type, &Codec.time_encode/1]
end
@impl Ecto.Adapter
def dumpers(:naive_datetime, type) do
[type, &Codec.naive_datetime_encode/1]
end
@impl Ecto.Adapter
def dumpers({:array, _}, type) do
[type, &Codec.json_encode/1]
end
@impl Ecto.Adapter
def dumpers({:map, _}, type) do
[&Ecto.Type.embedded_dump(type, &1, :json), &Codec.json_encode/1]
end
@impl Ecto.Adapter
def dumpers(:map, type) do
[type, &Codec.json_encode/1]
end
@impl Ecto.Adapter
def dumpers(_primitive, type) do
[type]
end
##
## HELPERS
##
defp storage_up_with_path(nil) do
raise ArgumentError,
"""
No SQLite database path specified. Please check the configuration for your Repo.
Your config/*.exs file should have something like this in it:
config :my_app, MyApp.Repo,
adapter: Ecto.Adapters.Exqlite,
database: "/path/to/sqlite/database"
"""
end
defp storage_up_with_path(db_path) do
if File.exists?(db_path) do
{:error, :already_up}
else
db_path |> Path.dirname() |> File.mkdir_p!()
{:ok, db} = Exqlite.Sqlite3.open(db_path)
:ok = Exqlite.Sqlite3.close(db)
end
end
defp dump_versions(config) do
table = config[:migration_source] || "schema_migrations"
# `.dump` command also returns CREATE TABLE which will clash with CREATE we already run in dump_schema
# So we set mode to insert which makes every SELECT statement to issue the result
# as the INSERT statements instead of pure text data.
case run_with_cmd("sqlite3", [
config[:database],
".mode insert #{table}",
"SELECT * FROM #{table}"
]) do
{output, 0} -> {:ok, output}
{output, _} -> {:error, output}
end
end
defp dump_schema(config) do
case run_with_cmd("sqlite3", [config[:database], ".schema"]) do
{output, 0} -> {:ok, output}
{output, _} -> {:error, output}
end
end
defp run_with_cmd(cmd, args) do
unless System.find_executable(cmd) do
raise "could not find executable `#{cmd}` in path, " <>
"please guarantee it is available before running ecto commands"
end
System.cmd(cmd, args, stderr_to_stdout: true)
end
end