Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: elixir-sqlite/sqlitex
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.6.0
Choose a base ref
...
head repository: elixir-sqlite/sqlitex
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Loading
Showing with 648 additions and 195 deletions.
  1. +69 −13 .circleci/config.yml
  2. +0 −2 .tool-versions
  3. +35 −0 README.md
  4. +85 −14 lib/sqlitex.ex
  5. +17 −2 lib/sqlitex/config.ex
  6. +26 −9 lib/sqlitex/query.ex
  7. +35 −16 lib/sqlitex/row.ex
  8. +127 −55 lib/sqlitex/server.ex
  9. +3 −3 lib/sqlitex/sql_builder.ex
  10. +24 −22 lib/sqlitex/statement.ex
  11. +11 −9 mix.exs
  12. +25 −23 mix.lock
  13. +8 −8 test/row_test.exs
  14. +31 −0 test/server_test.exs
  15. +3 −3 test/sql_builder_test.exs
  16. +41 −7 test/sqlitex_test.exs
  17. +106 −7 test/statement_test.exs
  18. +1 −1 test/test_database.exs
  19. +1 −1 test/test_helper.exs
82 changes: 69 additions & 13 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,81 @@
version: 2.0
version: 2.1

default_version: &default_version 1.8.1

jobs:
build:
parameters:
version:
description: Elixir Version
type: string
default: *default_version
parallelism: 1
docker:
- image: circleci/elixir:1.7.3
environment:
MIX_ENV: test
- image: elixir:<< parameters.version >>
working_directory: ~/app

steps:
- checkout
- restore_cache:
keys:
- v2-dependency-cache-{{ checksum "mix.lock" }}
- build-<<parameters.version>>
- run: mix local.hex --force
- run: mix local.rebar --force
- run: mix deps.get
- run: mix deps.compile
- run: mix compile
# not passing yet
# - run: mix credo --strict
- run: mix do deps.get, compile
- save_cache:
key: build-<<parameters.version>>
paths:
- "deps"
- "_build"
- "~/.mix"
- run: mix test

lint:
parameters:
version:
description: Elixir Version
type: string
default: *default_version
parallelism: 1
docker:
- image: elixir:<< parameters.version >>
working_directory: ~/app
steps:
- checkout # check out source code to working directory
- restore_cache:
keys:
- lint-<<parameters.version>>-{{ checksum "mix.lock" }}
- run: mix local.hex --force
- run: mix local.rebar --force
- run: mix do deps.get, compile
- run: mix dialyzer --halt-exit-status
- run: mix credo --strict
- run: mix coveralls.circle
- save_cache:
key: v2-dependency-cache-{{ checksum "mix.lock" }}
key: lint-<<parameters.version>>-{{ checksum "mix.lock" }}
paths:
- _build
- deps
- "deps"
- "_build"
- "~/.mix"

workflows:
version: 2.1
testing_all_versions:
jobs:
- build:
name: "Test Elixir 1.8.1"
version: 1.8.1
- build:
name: "Test Elixir 1.7.4"
version: 1.7.4
- build:
name: "Test Elixir 1.6.6"
version: 1.6.6
- build:
name: "Test Elixir 1.5.3"
version: 1.5.3
- build:
name: "Test Elixir 1.4.5"
version: 1.4.5
- lint:
name: "Check Formatting, Types and Coverage"
2 changes: 0 additions & 2 deletions .tool-versions

This file was deleted.

35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,11 @@
[![Hex.pm](https://img.shields.io/hexpm/v/sqlitex.svg)](https://hex.pm/packages/sqlitex)
[![Hex.pm](https://img.shields.io/hexpm/dt/sqlitex.svg)](https://hex.pm/packages/sqlitex)

# Notice

This library is sparsely maintined. If you are starting a new project, you will probably want to look into using
[exqlite](https://github.com/elixir-sqlite/exqlite) or it's matching Ecto Adatper [ecto_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3)

# Sqlitex

An Elixir wrapper around [esqlite](https://github.com/mmzeeman/esqlite). The main aim here is to provide convenient usage of SQLite databases.
@@ -62,5 +67,35 @@ Sqlitex.Server.query(Golf.DB,
ORDER BY g.played_at DESC LIMIT 10")
```

# Configuration

Sqlitex uses the Erlang library [esqlite](https://github.com/mmzeeman/esqlite)
which accepts a timeout parameter for almost all interactions with the database.
The default value for this timeout is 5000 ms. Many functions in Sqlitex accept
a `:db_timeout` option that is passed on to the esqlite calls and also defaults
to 5000 ms. If required, this default value can be overridden globally with the
following in your `config.exs`:

```elixir
config :sqlitex, db_timeout: 10_000 # or other positive integer number of ms
```

Another esqlite parameter is :db_chunk_size.
This is a count of rows to read from native sqlite and send to erlang process in one bulk.
For example, the table `mytable` has 1000 rows. We make the query to get all rows with `db_chunk_size: 500` parameter:
```elixir
Sqlitex.query(db, "select * from mytable", db_chunk_size: 500)
```
in this case all rows will be passed from native sqlite OS thread to the erlang process in two passes.
Each pass will contain 500 rows.
This parameter decrease overhead of transmitting rows from native OS sqlite thread to the erlang process by
chunking list of result rows.
Please, decrease this value if rows are heavy. Default value is 5000.
If you in doubt what to do with this parameter, please, do nothing. Default value is ok.
```elixir
config :sqlitex, db_chunk_size: 500 # if most of the database rows are heavy
```


# Looking for Ecto?
Check out the [SQLite Ecto2 adapter](https://github.com/Sqlite-Ecto/sqlite_ecto2)
99 changes: 85 additions & 14 deletions lib/sqlitex.ex
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ defmodule Sqlitex do
@type charlist :: char_list
end

@type connection :: {:connection, reference, binary()}
@type connection :: {:connection, reference(), reference()}
@type string_or_charlist :: String.t | charlist
@type sqlite_error :: {:error, {:sqlite_error, charlist}}

@@ -37,31 +37,46 @@ defmodule Sqlitex do
```
config :sqlitex, db_timeout: 10_000 # or other positive integer number of ms
```
Another esqlite parameter is :db_chunk_size.
This is a count of rows to read from native sqlite and send to erlang process in one bulk.
For example, the table `mytable` has 1000 rows. We make the query to get all rows with `db_chunk_size: 500` parameter:
```
Sqlitex.query(db, "select * from mytable", db_chunk_size: 500)
```
in this case all rows will be passed from native sqlite OS thread to the erlang process in two passes.
Each pass will contain 500 rows.
This parameter decrease overhead of transmitting rows from native OS sqlite thread to the erlang process by
chunking list of result rows.
Please, decrease this value if rows are heavy. Default value is 5000.
If you in doubt what to do with this parameter, please, do nothing. Default value is ok.
```
config :sqlitex, db_chunk_size: 500 # if most of the database rows are heavy
```
"""

alias Sqlitex.Config

@spec close(connection) :: :ok
@spec close(connection, Keyword.t) :: :ok
def close(db, opts \\ []) do
timeout = Keyword.get(opts, :db_timeout, Config.db_timeout())
:esqlite3.close(db, timeout)
:esqlite3.close(db, Config.db_timeout(opts))
end

@spec open(charlist | String.t) :: {:ok, connection} | {:error, {atom, charlist}}
@spec open(charlist | String.t, Keyword.t) :: {:ok, connection} | {:error, {atom, charlist}}
def open(path, opts \\ [])
def open(path, opts) when is_binary(path), do: open(string_to_charlist(path), opts)
def open(path, opts) do
timeout = Keyword.get(opts, :db_timeout, Config.db_timeout())
:esqlite3.open(path, timeout)
:esqlite3.open(path, Config.db_timeout(opts))
end

def with_db(path, fun, opts \\ []) do
{:ok, db} = open(path, opts)
res = fun.(db)
close(db, opts)
res
with {:ok, db} <- open(path, opts) do
res = fun.(db)
close(db, opts)
res
end
end

@doc """
@@ -72,27 +87,46 @@ defmodule Sqlitex do
* `action` -> `:insert | :update | :delete`
* `table` -> charlist of the table name. Example: `'posts'`
* `rowid` -> internal immutable rowid index of the row.
* `rowid` -> internal immutable rowid index of the row.
This is *NOT* the `id` or `primary key` of the row.
See the [official docs](https://www.sqlite.org/c3ref/update_hook.html).
"""
@spec set_update_hook(connection, pid, Keyword.t()) :: :ok | {:error, term()}
def set_update_hook(db, pid, opts \\ []) do
timeout = Keyword.get(opts, :db_timeout, Config.db_timeout())
:esqlite3.set_update_hook(pid, db, timeout)
:esqlite3.set_update_hook(pid, db, Config.db_timeout(opts))
end

@doc """
Send a raw SQL statement to the database
This function is intended for running fully-complete SQL statements.
No query preparation, or binding of values takes place.
This is generally useful for things like re-playing a SQL export back into the database.
"""
@spec exec(connection, string_or_charlist) :: :ok | sqlite_error
@spec exec(connection, string_or_charlist, Keyword.t) :: :ok | sqlite_error
def exec(db, sql, opts \\ []) do
timeout = Keyword.get(opts, :db_timeout, Config.db_timeout())
:esqlite3.exec(sql, db, timeout)
:esqlite3.exec(sql, db, Config.db_timeout(opts))
end

@doc "A shortcut to `Sqlitex.Query.query/3`"
@spec query(Sqlitex.connection, String.t | charlist) :: {:ok, [keyword]} | {:error, term()}
@spec query(Sqlitex.connection, String.t | charlist, [Sqlitex.Query.query_option]) :: {:ok, [keyword]} | {:error, term()}
def query(db, sql, opts \\ []), do: Sqlitex.Query.query(db, sql, opts)

@doc "A shortcut to `Sqlitex.Query.query!/3`"
@spec query!(Sqlitex.connection, String.t | charlist) :: [keyword]
@spec query!(Sqlitex.connection, String.t | charlist, [Sqlitex.Query.query_option]) :: [Enum.t]
def query!(db, sql, opts \\ []), do: Sqlitex.Query.query!(db, sql, opts)

@doc "A shortcut to `Sqlitex.Query.query_rows/3`"
@spec query_rows(Sqlitex.connection, String.t | charlist) :: {:ok, %{}} | Sqlitex.sqlite_error
@spec query_rows(Sqlitex.connection, String.t | charlist, [Sqlitex.Query.query_option]) :: {:ok, %{}} | Sqlitex.sqlite_error
def query_rows(db, sql, opts \\ []), do: Sqlitex.Query.query_rows(db, sql, opts)

@doc "A shortcut to `Sqlitex.Query.query_rows!/3`"
@spec query_rows!(Sqlitex.connection, String.t | charlist) :: %{}
@spec query_rows!(Sqlitex.connection, String.t | charlist, [Sqlitex.Query.query_option]) :: %{}
def query_rows!(db, sql, opts \\ []), do: Sqlitex.Query.query_rows!(db, sql, opts)

@doc """
@@ -117,9 +151,46 @@ defmodule Sqlitex do
exec(db, stmt, call_opts)
end

@doc """
Runs `fun` inside a transaction. If `fun` returns without raising an exception,
the transaction will be commited via `commit`. Otherwise, `rollback` will be called.
## Examples
iex> {:ok, db} = Sqlitex.open(":memory:")
iex> Sqlitex.with_transaction(db, fn(db) ->
...> Sqlitex.exec(db, "create table foo(id integer)")
...> Sqlitex.exec(db, "insert into foo (id) values(42)")
...> end)
iex> Sqlitex.query(db, "select * from foo")
{:ok, [[{:id, 42}]]}
"""
@spec with_transaction(Sqlitex.connection, (Sqlitex.connection -> any()), Keyword.t) :: any
def with_transaction(db, fun, opts \\ []) do
with :ok <- exec(db, "begin", opts),
{:ok, result} <- apply_rescuing(fun, [db]),
:ok <- exec(db, "commit", opts)
do
{:ok, result}
else
err ->
:ok = exec(db, "rollback", opts)
err
end
end

if Version.compare(System.version, "1.3.0") == :lt do
defp string_to_charlist(string), do: String.to_char_list(string)
else
defp string_to_charlist(string), do: String.to_charlist(string)
end

## Private Helpers

defp apply_rescuing(fun, args) do
try do
{:ok, apply(fun, args)}
rescue
error -> {:rescued, error, __STACKTRACE__}
end
end
end
19 changes: 17 additions & 2 deletions lib/sqlitex/config.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
defmodule Sqlitex.Config do
@moduledoc false

def db_timeout do
Application.get_env(:sqlitex, :db_timeout, 5_000)
@default_call_timeout 5_000
@default_db_timeout 5_000
@default_db_chunk_size 5_000

def call_timeout(opts \\ []) do
Keyword.get(opts, :call_timeout,
Keyword.get(opts, :timeout, # backward compatibility with the :timeout parameter
Application.get_env(:sqlitex, :call_timeout, @default_call_timeout)))
end

def db_timeout(opts \\ []) do
Keyword.get(opts, :db_timeout, Application.get_env(:sqlitex, :db_timeout, @default_db_timeout))
end

def db_chunk_size(opts \\ []) do
Keyword.get(opts, :db_chunk_size, Application.get_env(:sqlitex, :db_chunk_size, @default_db_chunk_size))
end

end
Loading