update
This commit is contained in:
602
phoenix/deps/ecto_sqlite3/lib/ecto/adapters/sqlite3.ex
Normal file
602
phoenix/deps/ecto_sqlite3/lib/ecto/adapters/sqlite3.ex
Normal file
@@ -0,0 +1,602 @@
|
||||
defmodule Ecto.Adapters.SQLite3 do
|
||||
@moduledoc """
|
||||
Adapter module for SQLite3.
|
||||
|
||||
It uses `Exqlite` for communicating to the database.
|
||||
|
||||
## Options
|
||||
|
||||
The adapter supports a superset of the options provided by the
|
||||
underlying `Exqlite` driver.
|
||||
|
||||
### Provided options
|
||||
|
||||
* `:database` - The path to the database. In memory is allowed. You can use
|
||||
`:memory` or `":memory:"` to designate that.
|
||||
* `:default_transaction_mode` - one of `:deferred` (default), `:immediate`, or
|
||||
`:exclusive`. If a mode is not specified in a call to `Repo.transaction/2`, this
|
||||
will be the default transaction mode.
|
||||
* `:journal_mode` - Sets the journal mode for the sqlite connection. Can be one of
|
||||
the following `:delete`, `:truncate`, `:persist`, `:memory`, `:wal`, or `:off`.
|
||||
Defaults to `:wal`.
|
||||
* `:temp_store` - Sets the storage used for temporary tables. Default is `:default`.
|
||||
Allowed values are `:default`, `:file`, `:memory`.
|
||||
* `:synchronous` - Can be `:extra`, `:full`, `:normal`, or `:off`. Defaults to `:normal`.
|
||||
* `:foreign_keys` - Sets if foreign key checks should be enforced or not. Can be
|
||||
`:on` or `:off`. Default is `:on`.
|
||||
* `:cache_size` - Sets the cache size to be used for the connection. This is an odd
|
||||
setting as a positive value is the number of pages in memory to use and a negative
|
||||
value is the size in kilobytes to use. Default is `-64000`.
|
||||
* `:cache_spill` - The cache_spill pragma enables or disables the ability of the
|
||||
pager to spill dirty cache pages to the database file in the middle of a
|
||||
transaction. By default it is `:on`, and for most applications, it should remain
|
||||
so.
|
||||
* `:case_sensitive_like` - whether LIKE is case-sensitive or not. Can be
|
||||
`:off` or `:on`. Defaults to `:off`.
|
||||
* `:auto_vacuum` - Defaults to `:none`. Can be `:none`, `:full` or `:incremental`.
|
||||
Depending on the database size, `:incremental` may be beneficial.
|
||||
* `:locking_mode` - Defaults to `:normal`. Allowed values are `:normal` or
|
||||
`:exclusive`. See [sqlite documentation][1] for more information.
|
||||
* `:secure_delete` - Defaults to `:off`. Can be `:off` or `:on`. If `:on`, it will
|
||||
cause SQLite3 to overwrite records that were deleted with zeros.
|
||||
* `:wal_auto_check_point` - Sets the write-ahead log auto-checkpoint interval.
|
||||
Default is `1000`. Setting the auto-checkpoint size to zero or a negative value
|
||||
turns auto-checkpointing off.
|
||||
* `:busy_timeout` - Sets the busy timeout in milliseconds for a connection.
|
||||
Default is `2000`.
|
||||
* `:pool_size` - the size of the connection pool. Defaults to `5`.
|
||||
* `:binary_id_type` - Defaults to `:string`. Determines how binary IDs are stored in
|
||||
the database and the type of `:binary_id` columns. See the
|
||||
[section on binary ID types](#module-binary-id-types) for more details.
|
||||
* `:uuid_type` - Defaults to `:string`. Determines the type of `:uuid` columns.
|
||||
Possible values and column types are the same as for
|
||||
[binary IDs](#module-binary-id-types).
|
||||
* `:map_type` - Defaults to `:string`. Determines the type of `:map` columns.
|
||||
Set to `:binary` to use the [JSONB](https://sqlite.org/jsonb.html)
|
||||
storage format.
|
||||
* `:array_type` - Defaults to `:string`. Determines the type of `:array` columns.
|
||||
Arrays are serialized using JSON. Set to `:binary` to use the
|
||||
[JSONB](https://sqlite.org/jsonb.html) storage format.
|
||||
* `:datetime_type` - Defaults to `:iso8601`. Determines how datetime fields are
|
||||
stored in the database. The allowed values are `:iso8601` and `:text_datetime`.
|
||||
`:iso8601` corresponds to a string of the form `YYYY-MM-DDThh:mm:ss` and
|
||||
`:text_datetime` corresponds to a string of the form `YYYY-MM-DD hh:mm:ss`
|
||||
* `:load_extensions` - list of paths identifying extensions to load. Defaults to `[]`.
|
||||
The provided list will be merged with the global extensions list, set on
|
||||
`:exqlite, :load_extensions`. Be aware that the path should handle pointing to a
|
||||
library compiled for the current architecture. See `Exqlite.Connection.connect/1`
|
||||
for more.
|
||||
|
||||
For more information about the options above, see [sqlite documentation][1]
|
||||
|
||||
### Differences between SQLite and Ecto SQLite defaults
|
||||
|
||||
For the most part, the defaults we provide above match the defaults that SQLite usually
|
||||
ships with. However, SQLite has conservative defaults due to its need to be strictly
|
||||
backwards compatible, so some of them do not necessarily match "best practices". Below
|
||||
are the defaults we provide above that differ from the normal SQLite defaults, along
|
||||
with rationale.
|
||||
|
||||
* `:journal_mode` - we use `:wal`, as it is vastly superior
|
||||
for concurrent access. SQLite usually defaults to `:delete`.
|
||||
See [SQLite documentation][2] for more info.
|
||||
* `:temp_store` - we use `:memory`, which increases performance a bit.
|
||||
SQLite usually defaults to `:file`.
|
||||
* `:foreign_keys` - we set it to `:on`, for better relational guarantees.
|
||||
This is also the default of the underlying `Exqlite` driver.
|
||||
SQLite usually defaults to `:off` for backwards compat.
|
||||
* `:busy_timeout` - we set it to `2000`, to better enable concurrent access.
|
||||
This is also the default of `Exqlite`. SQLite usually defaults to `0`.
|
||||
* `:cache_size` - we set it to `-64000`, to speed up access of data.
|
||||
SQLite usually defaults to `-2000`.
|
||||
|
||||
These defaults can of course be overridden, as noted above, to suit other needs.
|
||||
|
||||
[1]: https://www.sqlite.org/pragma.html
|
||||
[2]: https://sqlite.org/wal.html
|
||||
|
||||
### Binary ID types
|
||||
|
||||
The `:binary_id_type` configuration option allows configuring how `:binary_id` fields
|
||||
are stored in the database as well as the type of the column in which these IDs will
|
||||
be stored. The possible values are:
|
||||
|
||||
* `:string` - IDs are stored as strings, and the type of the column is `TEXT`. This is
|
||||
the default.
|
||||
* `:binary` - IDs are stored in their raw binary form, and the type of the column is `BLOB`.
|
||||
|
||||
The main differences between the two formats are as follows:
|
||||
* When stored as binary, UUIDs require much less space in the database. IDs stored as
|
||||
strings require 36 bytes each, while IDs stored as binary only require 16 bytes.
|
||||
* Because SQLite does not have a dedicated UUID type, most clients cannot represent
|
||||
UUIDs stored as binary in a human readable format. Therefore, IDs stored as strings
|
||||
may be easier to work with if manual manipulation is required.
|
||||
|
||||
## Limitations and caveats
|
||||
|
||||
There are some limitations when using Ecto with SQLite that one needs
|
||||
to be aware of. The ones listed below are specific to Ecto usage, but it
|
||||
is encouraged to also view the guidance on [when to use SQLite][4] provided
|
||||
by the SQLite documentation, as well.
|
||||
|
||||
### In memory robustness
|
||||
|
||||
When using the Ecto SQLite3 adapter with the database set to `:memory` it
|
||||
is possible that a crash in a process performing a query in the Repo will
|
||||
cause the database to be destroyed. This makes the `:memory` function
|
||||
unsuitable when it is expected to survive potential process crashes (for
|
||||
example a crash in a Phoenix request)
|
||||
|
||||
### Async Sandbox testing
|
||||
|
||||
The Ecto SQLite3 adapter does not support async tests when used with
|
||||
`Ecto.Adapters.SQL.Sandbox`. This is due to SQLite only allowing up one write
|
||||
transaction at a time, which often does not work with the Sandbox approach of wrapping
|
||||
each test in a transaction.
|
||||
|
||||
### LIKE match on BLOB columns
|
||||
|
||||
We have the `SQLITE_LIKE_DOESNT_MATCH_BLOBS` compile-time definition option set to true,
|
||||
as [recommended by SQLite][3]. This means you cannot do `LIKE` queries on `BLOB` columns.
|
||||
|
||||
### Case sensitivity
|
||||
|
||||
Case sensitivity for `LIKE` is off by default, and controlled by the `:case_sensitive_like`
|
||||
option outlined above.
|
||||
|
||||
However, for equality comparison, case sensitivity is always _on_.
|
||||
If you want to make a column not be case sensitive, for email storage for example, you
|
||||
can make it case insensitive by using the [`COLLATE NOCASE`][6] option in SQLite. This
|
||||
is configured via the `:collate` option.
|
||||
|
||||
So instead of:
|
||||
|
||||
add :email, :string
|
||||
|
||||
You would do:
|
||||
|
||||
add :email, :string, collate: :nocase
|
||||
|
||||
### Check constraints
|
||||
|
||||
SQLite3 supports specifying check constraints on the table or on the column definition.
|
||||
We currently only support adding a check constraint via a column definition, since the
|
||||
table definition approach only works at table-creation time and cannot be added at
|
||||
table-alter time. You can see more information in the SQLite3
|
||||
[CREATE TABLE documentation](https://sqlite.org/lang_createtable.html).
|
||||
|
||||
Because of this, you cannot add a constraint via the normal `Ecto.Migration.constraint/3`
|
||||
method, as that operates via `ALTER TABLE ADD CONSTRAINT`, and this type of `ALTER TABLE`
|
||||
operation SQLite3 does not support. You can however get the full functionality by
|
||||
adding a constraint at the column level, specifying the name and expression. Per the
|
||||
SQLite3 documentation, there is no _functional_ difference between a column or table
|
||||
constraint.
|
||||
|
||||
Thus, adding a check constraint for a new column is as simple as:
|
||||
|
||||
add :email, :string, check: %{name: "test_constraint", expr: "email != 'test@example.com'"}
|
||||
|
||||
### Handling foreign key constraints in changesets
|
||||
|
||||
Unfortunately, unlike other databases, SQLite3 does not provide the precise name of
|
||||
the constraint violated, but only the columns within that constraint (if it provides
|
||||
any information at all). Because of this, changeset functions like
|
||||
`Ecto.Changeset.foreign_key_constraint/3` may not work at all.
|
||||
|
||||
This is because the above functions depend on the Ecto Adapter returning the name of
|
||||
the violated constraint, which you annotate in your changeset so that Ecto can convert
|
||||
the constraint violation into the correct updated changeset when the constraint is hit
|
||||
during a `c:Ecto.Repo.update/2` or `c:Ecto.Repo.insert/2` operation. Since we cannot
|
||||
get the name of the violated constraint back from SQLite3 at `INSERT` or `UPDATE`
|
||||
time, there is no way to effectively use these changeset functions. This is a SQLite3
|
||||
limitation.
|
||||
|
||||
See [this GitHub issue](https://github.com/elixir-sqlite/ecto_sqlite3/issues/42) for
|
||||
more details.
|
||||
|
||||
### Schemaless queries
|
||||
|
||||
Using [schemaless Ecto queries][7] will not work well with SQLite. This is because
|
||||
the Ecto SQLite adapter relies heavily on the schema to support a rich array of Elixir
|
||||
types, despite the fact SQLite only has [five storage classes][5]. The query will still
|
||||
work and return data, but you will need to do this mapping on your own.
|
||||
|
||||
### Transaction mode
|
||||
|
||||
By default, [SQLite transactions][8] run in `DEFERRED` mode. However, in
|
||||
web applications with a balanced load of reads and writes, using `IMMEDIATE`
|
||||
mode may yield better performance.
|
||||
|
||||
Here are several ways to specify a different transaction mode:
|
||||
|
||||
**Pass `mode: :immediate` to `Repo.transaction/2`:** Use this approach to set
|
||||
the transaction mode for individual transactions.
|
||||
|
||||
Multi.new()
|
||||
|> Multi.run(:example, fn _repo, _changes_so_far ->
|
||||
# ... do some work ...
|
||||
end)
|
||||
|> Repo.transaction(mode: :immediate)
|
||||
|
||||
**Define custom transaction functions:** Create wrappers, such as
|
||||
`Repo.immediate_transaction/2` or `Repo.deferred_transaction/2`, to easily
|
||||
apply different modes where needed.
|
||||
|
||||
defmodule MyApp.Repo do
|
||||
def immediate_transaction(fun_or_multi) do
|
||||
transaction(fun_or_multi, mode: :immediate)
|
||||
end
|
||||
|
||||
def deferred_transaction(fun_or_multi) do
|
||||
transaction(fun_or_multi, mode: :deferred)
|
||||
end
|
||||
end
|
||||
|
||||
**Set a global default:** Configure `:default_transaction_mode` to apply a
|
||||
preferred mode for all transactions, unless explicitly passed a different
|
||||
`:mode` to `Repo.transaction/2`.
|
||||
|
||||
config :my_app, MyApp.Repo,
|
||||
database: "path/to/my/database.db",
|
||||
default_transaction_mode: :immediate
|
||||
|
||||
[3]: https://www.sqlite.org/compile.html
|
||||
[4]: https://www.sqlite.org/whentouse.html
|
||||
[5]: https://www.sqlite.org/datatype3.html
|
||||
[6]: https://www.sqlite.org/datatype3.html#collating_sequences
|
||||
[7]: https://hexdocs.pm/ecto/schemaless-queries.html
|
||||
[8]: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
"""
|
||||
|
||||
use Ecto.Adapters.SQL,
|
||||
driver: :exqlite
|
||||
|
||||
@behaviour Ecto.Adapter.Storage
|
||||
@behaviour Ecto.Adapter.Structure
|
||||
|
||||
alias Ecto.Adapters.SQLite3.Codec
|
||||
|
||||
@impl Ecto.Adapter.Storage
|
||||
def storage_down(options) do
|
||||
db_path = Keyword.fetch!(options, :database)
|
||||
|
||||
case File.rm(db_path) do
|
||||
:ok ->
|
||||
File.rm(db_path <> "-shm")
|
||||
File.rm(db_path <> "-wal")
|
||||
:ok
|
||||
|
||||
_otherwise ->
|
||||
{: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
|
||||
database = Keyword.get(options, :database)
|
||||
pool_size = Keyword.get(options, :pool_size)
|
||||
|
||||
cond do
|
||||
is_nil(database) ->
|
||||
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.SQLite3,
|
||||
database: "/path/to/sqlite/database"
|
||||
"""
|
||||
|
||||
File.exists?(database) ->
|
||||
{:error, :already_up}
|
||||
|
||||
database == ":memory:" && pool_size != 1 ->
|
||||
raise ArgumentError, """
|
||||
In memory databases must have a pool_size of 1
|
||||
"""
|
||||
|
||||
true ->
|
||||
{:ok, state} = Exqlite.Connection.connect(options)
|
||||
:ok = Exqlite.Connection.disconnect(:normal, state)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter.Migration
|
||||
def supports_ddl_transaction?, do: true
|
||||
|
||||
@impl Ecto.Adapter.Migration
|
||||
def lock_for_migrations(_meta, _options, fun) do
|
||||
fun.()
|
||||
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
|
||||
|
||||
@impl Ecto.Adapter.Structure
|
||||
def dump_cmd(args, opts \\ [], config) when is_list(config) and is_list(args) do
|
||||
run_with_cmd("sqlite3", ["-init", "/dev/null", config[:database] | args], opts)
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter.Schema
|
||||
def autogenerate(:id), do: nil
|
||||
def autogenerate(:embed_id), do: Ecto.UUID.generate()
|
||||
|
||||
def autogenerate(:binary_id) do
|
||||
case Application.get_env(:ecto_sqlite3, :binary_id_type, :string) do
|
||||
:string -> Ecto.UUID.generate()
|
||||
:binary -> Ecto.UUID.bingenerate()
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
## Loaders
|
||||
##
|
||||
|
||||
@default_datetime_type :iso8601
|
||||
|
||||
@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(:time, type) do
|
||||
[&Codec.time_decode/1, type]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def loaders(:utc_datetime_usec, type) do
|
||||
[&Codec.utc_datetime_decode/1, type]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def loaders(:utc_datetime, type) do
|
||||
[&Codec.utc_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(: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
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def loaders(:binary_id, type) do
|
||||
case Application.get_env(:ecto_sqlite3, :binary_id_type, :string) do
|
||||
:string -> [type]
|
||||
:binary -> [Ecto.UUID, type]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def loaders(:uuid, type) do
|
||||
case Application.get_env(:ecto_sqlite3, :uuid_type, :string) do
|
||||
:string -> []
|
||||
:binary -> [type]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def loaders(primitive_type, ecto_type) do
|
||||
loader_from_extension(primitive_type, ecto_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(:binary_id, type) do
|
||||
case Application.get_env(:ecto_sqlite3, :binary_id_type, :string) do
|
||||
:string -> [type]
|
||||
:binary -> [type, Ecto.UUID]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:uuid, type) do
|
||||
case Application.get_env(:ecto_sqlite3, :uuid_type, :string) do
|
||||
:string -> []
|
||||
:binary -> [type]
|
||||
end
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:time, type) do
|
||||
[type, &Codec.time_encode/1]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:utc_datetime, type) do
|
||||
dt_type = Application.get_env(:ecto_sqlite3, :datetime_type, @default_datetime_type)
|
||||
[type, &Codec.utc_datetime_encode(&1, dt_type)]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:utc_datetime_usec, type) do
|
||||
dt_type = Application.get_env(:ecto_sqlite3, :datetime_type, @default_datetime_type)
|
||||
[type, &Codec.utc_datetime_encode(&1, dt_type)]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:naive_datetime, type) do
|
||||
dt_type = Application.get_env(:ecto_sqlite3, :datetime_type, @default_datetime_type)
|
||||
[type, &Codec.naive_datetime_encode(&1, dt_type)]
|
||||
end
|
||||
|
||||
@impl Ecto.Adapter
|
||||
def dumpers(:naive_datetime_usec, type) do
|
||||
dt_type = Application.get_env(:ecto_sqlite3, :datetime_type, @default_datetime_type)
|
||||
[type, &Codec.naive_datetime_encode(&1, dt_type)]
|
||||
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, ecto_type) do
|
||||
dumper_from_extension(primitive_type, ecto_type)
|
||||
end
|
||||
|
||||
##
|
||||
## HELPERS
|
||||
##
|
||||
|
||||
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, cmd_opts \\ []) 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
|
||||
|
||||
cmd_opts = Keyword.put_new(cmd_opts, :stderr_to_stdout, true)
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user