Skip to content

Commit

Permalink
Merge aab4a9b into d1b1253
Browse files Browse the repository at this point in the history
  • Loading branch information
whossname committed Sep 13, 2019
2 parents d1b1253 + aab4a9b commit 6686c06
Show file tree
Hide file tree
Showing 26 changed files with 799 additions and 347 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Expand Up @@ -2,7 +2,9 @@ sudo: required

language: elixir
elixir:
- 1.6.5
- 1.8
otp_release:
- 20.0

services:
- docker
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
@@ -1,4 +1,4 @@
FROM elixir:1.6.5-slim
FROM elixir:1.8.0-slim

# --- Set Locale to en_US.UTF-8 ---

Expand Down
6 changes: 6 additions & 0 deletions bash_scripts/setup_test_db.sh
@@ -0,0 +1,6 @@
export MSSQL_UID=sa
export MSSQL_PWD='ThePa$$word'
docker stop test_mssql_server
docker rm test_mssql_server
docker run --name test_mssql_server -e 'ACCEPT_EULA=Y' -e SA_PASSWORD=$MSSQL_PWD -p 1433:1433 -d microsoft/mssql-server-linux
echo 'Created docker container test_mssql_server'
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
101 changes: 83 additions & 18 deletions lib/mssqlex.ex
Expand Up @@ -9,6 +9,19 @@ defmodule Mssqlex do

alias Mssqlex.Query
alias Mssqlex.Type
alias Mssqlex.Result
alias Mssqlex.Error
alias Mssqlex.Protocol

@max_rows 500

@typedoc """
A connection process name, pid or reference.
A connection reference is used when making multiple requests to the same
connection, see `transaction/3`.
"""
@type conn :: DBConnection.conn()
@type params :: [Type.param()]

@doc """
Connect to a MS SQL Server using ODBC.
Expand Down Expand Up @@ -45,9 +58,10 @@ defmodule Mssqlex do
iex> {:ok, pid} = Mssqlex.start_link(database: "mr_microsoft")
{:ok, #PID<0.70.0>}
"""
@spec start_link(Keyword.t()) :: {:ok, pid}
@spec start_link(Keyword.t()) :: {:ok, pid} | {:error, Error.t() | term}
def start_link(opts) do
DBConnection.start_link(Mssqlex.Protocol, opts)
ensure_deps_started!(opts)
DBConnection.start_link(Protocol, opts)
end

@doc """
Expand Down Expand Up @@ -101,15 +115,39 @@ defmodule Mssqlex do
supported due to adapter limitations. Select statements for columns
of these types must convert them to supported types (e.g. varchar).
"""
@spec query(pid(), binary(), [Type.param()], Keyword.t()) ::
{:ok, iodata(), Mssqlex.Result.t()}
def query(conn, statement, params, opts \\ []) do
DBConnection.prepare_execute(
conn,
%Query{name: "", statement: statement},
params,
opts
)

@spec query(conn, iodata, params, Keyword.t()) ::
{:ok, Result.t()} | {:error, Exception.t()}
def query(conn, statement, params \\ [], opts \\ [])

def query(conn, statement, params, opts) do
case Keyword.get(opts, :query_type) do
:text ->
query = %Query{
type: :text,
statement: statement,
ref: make_ref(),
num_params: 0
}

run_query(:execute, conn, query, [], opts)

type when type in [:binary, nil] ->
query = %Query{type: type, statement: statement}
run_query(:prepare_execute, conn, query, params, opts)
end
end

defp run_query(op, conn, query, params, opts) do
result = apply(DBConnection, op, [conn, query, params, opts])

case result do
{:ok, _, result} ->
{:ok, result}

{:error, _} = error ->
error
end
end

@doc """
Expand All @@ -118,13 +156,40 @@ defmodule Mssqlex do
Raises an error on failure. See `query/4` for details.
"""
@spec query!(pid(), binary(), [Type.param()], Keyword.t()) ::
{iodata(), Mssqlex.Result.t()}
Mssqlex.Result.t()
def query!(conn, statement, params, opts \\ []) do
DBConnection.prepare_execute!(
conn,
%Query{name: "", statement: statement},
params,
opts
)
case query(conn, statement, params, opts) do
{:ok, result} -> result
{:error, err} -> raise err
end
end

defp ensure_deps_started!(opts) do
if Keyword.get(opts, :ssl, false) and
not List.keymember?(:application.which_applications(), :ssl, 0) do
raise """
SSL connection can not be established because `:ssl` application is not started,
you can add it to `extra_application` in your `mix.exs`:
def application do
[extra_applications: [:ssl]]
end
"""
end
end

@spec child_spec(options :: Keyword.t()) :: Supervisor.Spec.spec()
def child_spec(opts) do
ensure_deps_started!(opts)
DBConnection.child_spec(Mssqlex.Protocol, opts)
end

def stream(%DBConnection{} = conn, query, params, options \\ []) do
options = Keyword.put_new(options, :max_rows, @max_rows)
%Mssqlex.Stream{conn: conn, query: query, params: params, options: options}
end

def prepare_execute(conn, name, statement, params, opts \\ []) do
query = %Query{name: name, statement: statement}
DBConnection.prepare_execute(conn, query, params, opts)
end
end
1 change: 0 additions & 1 deletion lib/mssqlex/error.ex
Expand Up @@ -18,7 +18,6 @@ defmodule Mssqlex.Error do
@not_allowed_in_transaction_messages [226, 574]

@doc false
@spec exception(binary()) :: t()
def exception({odbc_code, native_code, reason} = message) do
%__MODULE__{
message:
Expand Down
40 changes: 29 additions & 11 deletions lib/mssqlex/odbc.ex
Expand Up @@ -47,16 +47,26 @@ defmodule Mssqlex.ODBC do
| {:error, Exception.t()}
def query(pid, statement, params, opts) do
if Process.alive?(pid) do
statement = IO.iodata_to_binary(statement)

GenServer.call(
pid,
{:query, %{statement: IO.iodata_to_binary(statement), params: params}},
{:query, %{statement: statement, params: params}},
Keyword.get(opts, :timeout, 5000)
)
else
{:error, %Mssqlex.Error{message: :no_connection}}
end
end

def describe(pid, table) do
if Process.alive?(pid) do
GenServer.call(pid, {:describe, table})
else
{:error, %Mssqlex.Error{message: :no_connection}}
end
end

@doc """
Commits a transaction on the ODBC driver.
Expand Down Expand Up @@ -126,26 +136,34 @@ defmodule Mssqlex.ODBC do
def handle_call(
{:query, %{statement: statement, params: params}},
_from,
state
pid
) do
{:reply,
handle_errors(:odbc.param_query(state, to_charlist(statement), params)),
state}
resp =
pid
|> :odbc.param_query(to_charlist(statement), params)
|> handle_errors()

{:reply, resp, pid}
end

@doc false
def handle_call({:describe, table}, _from, pid) do
{:reply, handle_errors(:odbc.describe_table(pid, table)), pid}
end

@doc false
def handle_call(:commit, _from, state) do
{:reply, handle_errors(:odbc.commit(state, :commit)), state}
def handle_call(:commit, _from, pid) do
{:reply, handle_errors(:odbc.commit(pid, :commit)), pid}
end

@doc false
def handle_call(:rollback, _from, state) do
{:reply, handle_errors(:odbc.commit(state, :rollback)), state}
def handle_call(:rollback, _from, pid) do
{:reply, handle_errors(:odbc.commit(pid, :rollback)), pid}
end

@doc false
def terminate(_reason, state) do
:odbc.disconnect(state)
def terminate(_reason, pid) do
:odbc.disconnect(pid)
end

defp handle_errors({:error, reason}), do: {:error, Error.exception(reason)}
Expand Down

0 comments on commit 6686c06

Please sign in to comment.