Packages
jamdb_oracle
0.1.2
0.5.12
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
retired
0.5.1
retired
0.5.0
retired
0.4.12
retired
0.4.11
retired
0.4.10
retired
0.4.9
retired
0.4.8
retired
0.4.7
retired
0.4.6
retired
0.4.5
retired
0.4.4
retired
0.4.3
retired
0.4.2
retired
0.4.1
retired
0.4.0
retired
0.3.10
retired
0.3.9
retired
0.3.8
retired
0.3.7
retired
0.3.6
retired
0.3.5
retired
0.3.4
retired
0.3.3
retired
0.3.2
retired
0.3.1
retired
0.3.0
retired
0.2.7
retired
0.2.6
retired
0.2.5
retired
0.2.4
retired
0.2.3
retired
0.2.2
retired
0.2.1
retired
0.2.0
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
0.0.10
retired
0.0.9
retired
0.0.8
retired
0.0.7
retired
0.0.6
retired
0.0.5
retired
0.0.4
retired
0.0.3
retired
0.0.2
retired
0.0.1
retired
Erlang driver and Ecto adapter for Oracle
Retired package: Deprecated
Current section
Files
Jump to
Current section
Files
lib/jamdb_oracle.ex
defmodule Jamdb.Oracle do
@moduledoc """
Oracle driver for Elixir.
It relies on `DBConnection` to provide pooling, prepare, execute and more.
"""
@behaviour DBConnection
@doc """
Connect to the database. Return `{:ok, pid}` on success or
`{:error, term}` on failure.
## Options
* `:hostname` - Server hostname (Name or IP address of the database server)
* `:port` - Server port (Number of the port where the server listens for requests)
* `:database` - Database (Database service name or SID with colon as prefix)
* `:username` - Username (Name for the connecting user)
* `:password` - User password (Password for the connecting user)
* `:parameters` - Keyword list of connection parameters
* `:socket_options` - Options to be given to the underlying socket
* `:timeout` - The default timeout to use on queries, defaults to `15000`
* `:charset` - Name that is used in multibyte encoding
This callback is called in the connection process.
"""
@callback connect(opts :: Keyword.t) ::
{:ok, pid} | {:error, term}
def connect(opts) do
database = Keyword.fetch!(opts, :database) |> to_charlist
env = if( hd(database) == ?:, do: [sid: tl(database)], else: [service_name: database] )
|> Keyword.put_new(:host, Keyword.fetch!(opts, :hostname) |> to_charlist)
|> Keyword.put_new(:port, Keyword.fetch!(opts, :port))
|> Keyword.put_new(:user, Keyword.fetch!(opts, :username) |> to_charlist)
|> Keyword.put_new(:password, Keyword.fetch!(opts, :password) |> to_charlist)
|> Keyword.put_new(:timeout, Keyword.fetch!(opts, :timeout))
params = if( Keyword.has_key?(opts, :parameters) == true,
do: opts[:parameters], else: [] )
sock_opts = if( Keyword.has_key?(opts, :socket_options) == true,
do: [socket_options: opts[:socket_options]], else: [] )
:jamdb_oracle.start_link(sock_opts ++ params ++ env)
end
@doc """
Disconnect from the database. Return `:ok`.
This callback is called in the connection process.
"""
@callback disconnect(err :: term, s :: pid) :: :ok
def disconnect(_err, s) do
:jamdb_oracle.stop(s)
end
@doc """
Runs custom SQL query on given pid.
In case of success, it must return an `:ok` tuple containing result struct. Its fields are:
* `:columns` - The column names
* `:num_rows` - The number of fetched or affected rows
* `:rows` - The result set as list
## Examples
iex> Jamdb.Oracle.query(s, 'select 1+:1, sysdate, rowid from dual where 1=:1 ',[1])
{:ok, %{num_rows: 1, rows: [[{2}, {{2016, 8, 1}, {13, 14, 15}}, 'AAAACOAABAAAAWJAAA']]}}
"""
@callback query(s :: pid, sql :: String.t, params :: [term] | map) ::
{:ok, term} | {:error, term}
def query(s, sql, params \\ []) do
case :jamdb_oracle.sql_query(s, {sql, params}) do
{:ok, [{_, columns, _, rows}]} ->
{:ok, %{num_rows: length(rows), rows: rows, columns: columns}, s}
{:ok, [{_, 0, rows}]} -> {:ok, %{num_rows: length(rows), rows: rows}, s}
{:ok, [{_, code, msg}]} -> {:error, %{code: code, message: msg}, s}
{:ok, [{_, num_rows}]} -> {:ok, %{num_rows: num_rows, rows: nil}, s}
{:ok, result} -> {:ok, result, s}
{:error, _, err} -> {:error, err, s}
end
end
@doc false
def handle_execute(query, params, opts, s) do
%Jamdb.Oracle.Query{statement: statement} = query
returning = Keyword.get(opts, :returning, []) |> Enum.filter(& is_tuple(&1))
query(s, statement |> to_charlist, Enum.concat(params, returning))
end
@doc false
def handle_prepare(%Jamdb.Oracle.Query{statement: %Jamdb.Oracle.Query{} = query}, opts, s) do
{:ok, query, s}
end
def handle_prepare(query, opts, s) do
{:ok, query, s}
end
@doc false
def handle_begin(opts, s) do
case Keyword.get(opts, :mode, :transaction) do
:transaction -> query(s, 'SAVEPOINT tran')
:savepoint -> query(s, 'SAVEPOINT '++(Keyword.get(opts, :name, :svpt) |> to_charlist))
end
end
@doc false
def handle_commit(opts, s) do
query(s, 'COMMIT')
end
@doc false
def handle_rollback(opts, s) do
case Keyword.get(opts, :mode, :transaction) do
:transaction -> query(s, 'ROLLBACK TO tran')
:savepoint -> query(s, 'ROLLBACK TO '++(Keyword.get(opts, :name, :svpt) |> to_charlist))
end
end
@doc false
def handle_declare(query, params, opts, s) do
{:ok, params, s}
end
@doc false
def handle_first(query, params, opts, s) do
case handle_execute(query, params, opts, s) do
{:ok, result, s} -> {:deallocate, result, s}
{:error, err, s} -> {:error, error!(err), s}
end
end
@doc false
def handle_next(query, cursor, opts, s) do
{:deallocate, nil, s}
end
@doc false
def handle_deallocate(query, cursor, opts, s) do
{:ok, nil, s}
end
@doc false
def handle_close(query, opts, s) do
{:ok, nil, s}
end
@doc false
def handle_info(msg, s) do
{:ok, s}
end
@doc false
def checkin(s) do
{:ok, s}
end
@doc false
def checkout(s) do
{:ok, s}
end
@doc false
def ping(s) do
case query(s, 'PING') do
{:ok, _, s} -> {:ok, s}
disconnect -> disconnect
end
end
defp error!(msg) do
DBConnection.ConnectionError.exception("#{inspect msg}")
end
end
defimpl DBConnection.Query, for: Jamdb.Oracle.Query do
def parse(query, _), do: query
def describe(query, _), do: query
def decode(_, %{rows: []} = result, _), do: result
def decode(_, %{rows: rows} = result, opts) when rows != nil,
do: %{result | rows: Enum.map(rows, fn row -> decode(row, opts[:decode_mapper]) end)}
def decode(_, result, _), do: result
defp decode(row, nil), do: Enum.map(row, fn elem -> decode(elem) end)
defp decode(row, mapper), do: mapper.(decode(row, nil))
defp decode(:null), do: nil
defp decode({elem}) when is_number(elem), do: elem
defp decode({date, time}) when is_tuple(date), do: to_naive({date, time})
defp decode({date, time, tz}) when is_tuple(date) and is_list(tz), do: to_utc({date, time, tz})
defp decode({date, time, _}) when is_tuple(date), do: to_naive({date, time})
defp decode(elem) when is_list(elem), do: to_binary(elem)
defp decode(elem), do: elem
def encode(_, [], _), do: []
def encode(_, params, opts) do
charset = if( Keyword.has_key?(opts, :charset) == true,
do: Enum.member?(["al16utf16","ja16euc","zhs16gbk","zht16big5","zht16mswin950"],
opts[:charset]), else: false )
Enum.map(params, fn elem -> encode(elem, charset) end)
end
defp encode(nil, _), do: :null
defp encode(%Decimal{} = decimal, _), do: Decimal.to_float(decimal)
defp encode(%Ecto.Query.Tagged{value: elem}, _), do: elem
defp encode(elem, false) when is_binary(elem), do: elem |> to_charlist
defp encode(elem, _), do: elem
defp expr(list) when is_list(list) do
Enum.map(list, fn
:null -> nil
elem -> elem
end)
end
defp to_binary(list) when is_list(list) do
try do
:binary.list_to_bin(list)
rescue
ArgumentError ->
Enum.map(expr(list), fn
elem when is_list(elem) -> expr(elem)
other -> other
end) |> Enum.join
end
end
defp to_naive({{year, mon, day}, {hour, min, sec}}) when is_integer(sec),
do: {{year, mon, day}, {hour, min, sec}}
defp to_naive({{year, mon, day}, {hour, min, sec}}),
do: {{year, mon, day}, parse_time({hour, min, sec})}
defp to_utc({date, time, tz}) do
{hour, min, sec, usec} = parse_time(time)
offset = parse_offset(to_string(tz))
seconds = :calendar.datetime_to_gregorian_seconds({date, {hour, min, sec}})
{{year, mon, day}, {hour, min, sec}} = :calendar.gregorian_seconds_to_datetime(seconds + offset)
%DateTime{year: year, month: mon, day: day, hour: hour, minute: min, second: sec,
microsecond: {usec, 6}, std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: to_string(tz)}
end
defp parse_time({hour, min, sec}),
do: {hour, min, trunc(sec), trunc((sec - trunc(sec)) * 1000000)}
defp parse_offset(tz) do
case Calendar.ISO.parse_offset(tz) do
{offset, ""} when is_integer(offset) -> offset
_ -> 0
end
end
end