Skip to content

Commit e9afa46

Browse files
authored
Merge pull request #13 from altenwald/feature/elixir
Support for Elixir
2 parents cc07211 + c3952cb commit e9afa46

File tree

11 files changed

+291
-16
lines changed

11 files changed

+291
-16
lines changed

README.md

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,26 @@ DBI for Erlang
66
[![License: LGPL 2.1](https://img.shields.io/badge/License-GNU%20Lesser%20General%20Public%20License%20v2.1-blue.svg)](https://raw.githubusercontent.com/altenwald/dbi/master/COPYING)
77
[![Hex](https://img.shields.io/hexpm/v/dbi.svg)](https://hex.pm/packages/dbi)
88

9-
Database Interface for Erlang. This is an abstract implementation to use the most common database libraries ([p1_mysql][1], [epgsql][2] and [esqlite][4], and others you want) to use with standard SQL in your programs and don't worry about if you need to change between the main databases in the market.
9+
Database Interface for Erlang and Elixir. This is an abstract implementation to use the most common database libraries ([p1_mysql][1], [epgsql][2] and [esqlite][4], and others you want) to use with standard SQL in your programs and don't worry about if you need to change between the main databases in the market.
1010

1111
### Install (rebar3)
1212

1313
To use it, with rebar, you only need to add the dependency to the rebar.config file:
1414

1515
```erlang
1616
{deps, [
17-
{dbi, "0.1.0"}
17+
{dbi, "0.2.0"}
1818
]}
1919
```
2020

21+
### Install (mix)
22+
23+
To use it, with mix, you only need to add the dependency to the mix.exs file:
24+
25+
```elixir
26+
{:dbi, "~> 0.2.0"}
27+
```
28+
2129
### Configuration
2230

2331
The configuration is made in the configuration file (`sys.config` or `app.config`) so, you can add a new block for config the database connection as follow:
@@ -49,21 +57,59 @@ The configuration is made in the configuration file (`sys.config` or `app.config
4957

5058
The available types in this moment are: `mysql`, `pgsql` and `sqlite`.
5159

60+
In case you're using Elixir, you can define the configuration for your project in this way:
61+
62+
```elixir
63+
confg :dbi, mydatabase: [
64+
type: :mysql,
65+
host: 'localhost',
66+
user: 'root',
67+
pass: 'root',
68+
database: 'mydatabase',
69+
poolsize: 10
70+
],
71+
mylocaldb: [
72+
type: :sqlite,
73+
database: ':memory'
74+
],
75+
mystrongdb: [
76+
type: :pgsql,
77+
host: 'localhost',
78+
user: 'root',
79+
pass: 'root',
80+
database: 'mystrongdb',
81+
poolsize: 100
82+
]
83+
```
84+
5285
### Using DBI
5386

54-
To do a query:
87+
To do a query (Erlang):
5588

5689
```erlang
5790
{ok, Count, Rows} = dbi:do_query(mydatabase, "SELECT * FROM users", []),
5891
```
5992

60-
Or with params:
93+
Elixir:
94+
95+
```elixir
96+
{:ok, count, rows} = DBI.do_query(:mydatabase, "SELECT * FROM users", [])
97+
```
98+
99+
Or with params (Erlang):
61100

62101
```erlang
63102
{ok, Count, Rows} = dbi:do_query(mydatabase,
64103
"SELECT * FROM users WHERE id = $1", [12]),
65104
```
66105

106+
Elixir:
107+
108+
```elixir
109+
{:ok, count, rows} = DBI.do_query(:mydatabase,
110+
"SELECT * FROM users WHERE id = $1", [12])
111+
```
112+
67113
Rows has the format: `[{field1, field2, ..., fieldN}, ...]`
68114

69115
**IMPORTANT** the use of $1..$100 in the query is extracted from pgsql, in mysql and sqlite is converted to the `?` syntax so, if you write this query:
@@ -73,19 +119,34 @@ Rows has the format: `[{field1, field2, ..., fieldN}, ...]`
73119
"UPDATE users SET name = $2 WHERE id = $1", [12, "Mike"]),
74120
```
75121

122+
Elixir:
123+
124+
```elixir
125+
{:ok, count, rows} = DBI.do_query(:mydatabase,
126+
"UPDATE users SET name = $2 WHERE id = $1", [12, "Mike"])
127+
```
128+
76129
That should works well in pgsql, but **NOT for mysql and NOT for sqlite**. For avoid this situations, the best to do is **always keep the order of the params**.
77130

78131
### Delayed or Queued queries
79132

80-
If you want to create a connection to send only commands like INSERT, UPDATE or DELETE but without saturate the database (and run out database connections in the pool) you can use `dbi_delayed`:
133+
If you want to create a connection to send only commands like INSERT, UPDATE or DELETE but without saturate the database (and run out database connections in the pool) you can use `dbi_delayed` (Erlang):
81134

82135
```erlang
83136
{ok, PID} = dbi_delayed:start_link(delay_myconn, myconn),
84137
dbi_delayed:do_query(delay_myconn,
85138
"INSERT INTO my tab VALUES ($1, $2)", [N1, N2]),
86139
```
87140

88-
This use only one connection from the pool `myconn`, when the query ends then `dbi_delayed` gets another query to run from the queue. You get statistics about the progress and the queue size:
141+
Elixir:
142+
143+
```elixir
144+
{:ok, pid} = DBI.Delayed.start_link(:delay_myconn, :myconn)
145+
DBI.Delayed.do_query(:delay_myconn,
146+
"INSERT INTO my tab VALUES ($1, $2)", [n1, n2])
147+
```
148+
149+
This use only one connection from the pool `myconn`, when the query ends then `dbi_delayed` gets another query to run from the queue. You get statistics about the progress and the queue size (Erlang):
89150

90151
```erlang
91152
dbi_delayed:stats(delay_myconn).
@@ -96,6 +157,17 @@ dbi_delayed:stats(delay_myconn).
96157
]
97158
```
98159

160+
Elixir:
161+
162+
```elixir
163+
DBI.Delayed.stats(:delay_myconn)
164+
[
165+
size: 0,
166+
query_error: 0,
167+
query_ok: 1
168+
]
169+
```
170+
99171
The delayed can be added to the configuration:
100172

101173
```erlang
@@ -112,6 +184,20 @@ The delayed can be added to the configuration:
112184
]}
113185
```
114186

187+
Elixir:
188+
189+
```elixir
190+
config :dbi, mydatabase: [
191+
type: :mysql,
192+
host: 'localhost',
193+
user: 'root',
194+
pass: 'root',
195+
database: 'mydatabase',
196+
poolsize: 10,
197+
delayed: :delay_myconn
198+
]
199+
```
200+
115201
### Cache queries
116202

117203
Another thing you can do is use a cache for SQL queries. The cache store the SQL as `key` and the result as `value` and keep the values for the time you specify in the configuration file:
@@ -130,6 +216,20 @@ Another thing you can do is use a cache for SQL queries. The cache store the SQL
130216
]}
131217
```
132218

219+
Elixir:
220+
221+
```elixir
222+
config :dbi, mydatabase: [
223+
type: :mysql,
224+
host: 'localhost',
225+
user: 'root',
226+
pass: 'root',
227+
database: 'mydatabase',
228+
poolsize: 10,
229+
cache: 5
230+
]
231+
```
232+
133233
The cache param is in seconds. The ideal time to keep the cache values depends on the size of your tables, the data to store in the cache and how frequent are the changes in that data. For avoid flood and other issues due to fast queries or a lot of queries in little time you can use 5 or 10 seconds. To store the information about constants or other data without frequent changes you can use 3600 (one hour) or more time.
134234

135235
To use the cache you should to use the following function from `dbi_cache`:
@@ -138,13 +238,26 @@ To use the cache you should to use the following function from `dbi_cache`:
138238
dbi_cache:do_query(mydatabase, "SELECT items FROM table"),
139239
```
140240

241+
Elixir:
242+
243+
```elixir
244+
DBI.Cache.do_query(:mydatabase, "SELECT items FROM table")
245+
```
246+
141247
You can use `do_query/2` or `do_query/3` if you want to use params. And if you want to use a specific TTL (time-to-live) for your query, you can use `do_query/4`:
142248

143249
```erlang
144250
dbi_cache:do_query(mydatabase,
145251
"SELECT items FROM table", [], 3600),
146252
```
147253

254+
Elixir:
255+
256+
```elixir
257+
DBI.Cache.do_query(:mydatabase,
258+
"SELECT items FROM table", [], 3600)
259+
```
260+
148261
Enjoy!
149262

150263
[1]: https://github.com/processone/p1_mysql

lib/dbi.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
defmodule DBI do
2+
use Application
3+
4+
# wrap from dbi_app.erl
5+
def start(type, args), do: :dbi_app.start(type, args)
6+
def stop(modules), do: :dbi_app.stop(modules)
7+
8+
# wrap from dbi.erl
9+
def start(), do: :dbi.start()
10+
def do_query(pool, query), do: :dbi.do_query(pool, query)
11+
def do_query(pool, query, args), do: :dbi.do_query(pool, query, args)
12+
def connect(type, host, port, user, pass, database, poolname) do
13+
:dbi.connect(type, host, port, user, pass, database, poolname)
14+
end
15+
def connect(type, host, port, user, pass, db, poolname, poolsize, extra) do
16+
:dbi.connect(type, host, port, user, pass, db, poolname, poolsize, extra)
17+
end
18+
19+
defmodule Cache do
20+
def do_query(ref, query), do: :dbi_cache.do_query(ref, query)
21+
def do_query(ref, query, args), do: :dbi_cache.do_query(ref, query, args)
22+
def do_query(ref, query, args, ttl) do
23+
:dbi_cache.do_query(ref, query, args, ttl)
24+
end
25+
end
26+
27+
defmodule Delayed do
28+
def start_link(ref, conn), do: :dbi_delayed.start_link(ref, conn)
29+
def do_query(ref, query, args), do: :dbi_delayed.do_query(ref, query, args)
30+
def do_query(ref, query), do: :dbi_delayed.do_query(ref, query)
31+
def stats(ref), do: :dbi_delayed.stats(ref)
32+
end
33+
end

mix.exs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule DBI.Mixfile do
2+
use Mix.Project
3+
4+
def project do
5+
[app: :dbi,
6+
version: get_version(),
7+
name: "DBI",
8+
description: "DataBase Interface for Erlang",
9+
package: package(),
10+
source_url: "https://github.com/altenwald/dbi",
11+
elixir: "~> 1.3",
12+
compilers: Mix.compilers,
13+
build_embedded: Mix.env == :prod,
14+
start_permanent: Mix.env == :prod,
15+
deps: deps()]
16+
end
17+
18+
def application do
19+
env = if Mix.env == :test do
20+
[testdb1: [type: :sqlite,
21+
database: ':memory:'],
22+
testdb2: [type: :sqlite,
23+
database: ':memory:',
24+
cache: 3],
25+
testdb3: [type: :sqlite,
26+
database: ':memory:',
27+
delayed: :mydelayed]
28+
]
29+
else
30+
[]
31+
end
32+
[applications: [:crypto, :public_key, :asn1, :ssl],
33+
mod: {DBI, []},
34+
env: env]
35+
end
36+
37+
defp deps do
38+
[{:epgsql, "~> 3.4.0"},
39+
{:p1_mysql, "~> 1.0.4"},
40+
{:esqlite, "~> 0.2.3"},
41+
{:cache, "~> 2.2.0"},
42+
{:poolboy, "~> 1.5.1"}]
43+
end
44+
45+
defp package do
46+
[files: ["lib", "mix.exs", "README*", "LICENSE*"],
47+
maintainers: ["Manuel Rubio"],
48+
licenses: ["LGPL 2.1"],
49+
links: %{"GitHub" => "https://github.com/altenwald/dbi"}]
50+
end
51+
52+
defp get_version do
53+
retrieve_version_from_git()
54+
|> String.split("-")
55+
|> case do
56+
[tag] -> tag
57+
[tag, _num_commits, commit] -> "#{tag}-#{commit}"
58+
end
59+
end
60+
61+
defp retrieve_version_from_git do
62+
System.cmd("git", ["describe", "--always", "--tags"])
63+
|> Tuple.to_list
64+
|> List.first
65+
|> String.strip
66+
end
67+
end

mix.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
%{"cache": {:hex, :cache, "2.2.0", "3c11dbf4cd8fcd5787c95a5fb2a04038e3729cfca0386016eea8c953ab48a5ab", [:rebar3], [], "hexpm"},
2+
"epgsql": {:hex, :epgsql, "3.4.0", "39d473b83d74329a88f8fb1f99ad24c7a2329fd753957c488808cb7c0fc8164e", [:rebar3], [], "hexpm"},
3+
"esqlite": {:hex, :esqlite, "0.2.3", "1a8b60877fdd3d50a8a84b342db04032c0231cc27ecff4ddd0d934485d4c0cd5", [:rebar3], [], "hexpm"},
4+
"p1_mysql": {:hex, :p1_mysql, "1.0.4", "7b9d7957a9d031813a0e6bcea5a7f5e91b54db805a92709a445cf75cf934bc1d", [:rebar3], [], "hexpm"},
5+
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}}

src/dbi.erl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ connect(Type, Host, Port, User, Pass, Database, Poolname, Poolsize, Extra) ->
6767
{database, Database}, {poolsize, Poolsize} | Extra
6868
],
6969
application:set_env(dbi, Poolname, DBConf),
70-
Module:init(Host, Port, User, Pass, Database, Poolname, Poolsize, Extra),
71-
Module:run().
70+
Module:init(Host, Port, User, Pass, Database, Poolname, Poolsize, Extra).
7271

7372
-spec connect( Type::atom(),
7473
Host :: string(), Port :: integer(), User :: string(),

src/dbi_mysql.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ start_link(ConnData) ->
2929
init(Host, Port, User, Pass, Database, Poolname, Poolsize, Extra) ->
3030
application:start(p1_mysql),
3131
MaxOverflow = proplists:get_value(max_overflow, Extra, ?DEFAULT_MAX_OVERFLOW),
32-
ConnData = [Host, dbi_utils:default(Port, ?DEFAULT_PORT),
32+
ConnData = [Host, dbi_query:default(Port, ?DEFAULT_PORT),
3333
User, Pass, Database, undefined],
3434
PoolArgs = [{name, {local, Poolname}}, {worker_module, ?MODULE},
35-
{size, dbi_utils:default(Poolsize, ?DEFAULT_POOLSIZE)},
35+
{size, dbi_query:default(Poolsize, ?DEFAULT_POOLSIZE)},
3636
{max_overflow, MaxOverflow}],
3737
ChildSpec = poolboy:child_spec(Poolname, PoolArgs, ConnData),
3838
supervisor:start_child(?DBI_SUP, ChildSpec),
@@ -53,7 +53,7 @@ do_query(PoolDB, SQL, Params) when is_list(SQL) ->
5353
do_query(PoolDB, list_to_binary(SQL), Params);
5454

5555
do_query(PoolDB, RawSQL, Params) when is_binary(RawSQL) ->
56-
SQL = dbi_utils:resolve(RawSQL),
56+
SQL = dbi_query:resolve(RawSQL),
5757
poolboy:transaction(PoolDB, fun(PID) ->
5858
case p1_mysql_conn:squery(PID, SQL, self(), Params) of
5959
{data, Result} ->

src/dbi_pgsql.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ init(Host, Port, User, Pass, Database, Poolname, Poolsize, Extra) ->
3131
MaxOverflow = proplists:get_value(max_overflow, Extra, ?DEFAULT_MAX_OVERFLOW),
3232
DataConn = [Host, User, Pass,
3333
[{database, Database},
34-
{port, dbi_utils:default(Port, ?DEFAULT_PORT)}] ++ Extra],
34+
{port, dbi_query:default(Port, ?DEFAULT_PORT)}] ++ Extra],
3535
PoolArgs = [{name, {local, Poolname}}, {worker_module, ?MODULE},
36-
{size, dbi_utils:default(Poolsize, ?DEFAULT_POOLSIZE)},
36+
{size, dbi_query:default(Poolsize, ?DEFAULT_POOLSIZE)},
3737
{max_overflow, MaxOverflow}],
3838
ChildSpec = poolboy:child_spec(Poolname, PoolArgs, DataConn),
3939
supervisor:start_child(?DBI_SUP, ChildSpec),

src/dbi_utils.erl renamed to src/dbi_query.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
-module(dbi_utils).
1+
-module(dbi_query).
22
-author('[email protected]').
33

44
-export([

src/dbi_sqlite.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ do_query(PoolDB, SQL, Params) when is_list(SQL) ->
3535
do_query(PoolDB, list_to_binary(SQL), Params);
3636

3737
do_query(PoolDB, RawSQL, Params) when is_binary(RawSQL) ->
38-
SQL = dbi_utils:resolve(RawSQL),
38+
SQL = dbi_query:resolve(RawSQL),
3939
{ok, Conn} = dbi_sqlite_server:get_database(PoolDB),
40-
case dbi_utils:sql_type(SQL) of
40+
case dbi_query:sql_type(SQL) of
4141
dql ->
4242
case catch esqlite3:q(SQL, Params, Conn) of
4343
Rows when is_list(Rows) -> {ok, length(Rows), Rows};

0 commit comments

Comments
 (0)