Skip to content

Commit

Permalink
add initial files
Browse files Browse the repository at this point in the history
  • Loading branch information
nakagami committed Feb 9, 2019
1 parent de402bb commit b1a3c16
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 0 deletions.
92 changes: 92 additions & 0 deletions lib/firebirdex.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
defmodule Firebirdex do
alias Firebirdex.Query

@type conn :: DBConnection.conn()

@spec start_link(keyword()) :: {:ok, pid()} | {:error, Firebirdex.Error.t()}
def start_link(opts) do
DBConnection.start_link(Firebirdex.Protocol, opts)
end

@spec query(conn, iodata, list, keyword()) ::
{:ok, Firebirdex.Result.t()} | {:error, Firebirdex.Error.t()}
def query(conn, statement, params \\ [], opts \\ []) do
query = %Query{name: "", statement: statement}
case DBConnection.prepare_execute(conn, query, params, opts) do
{:ok, _, result} ->
{:ok, result}
{:error, _} = error ->
error
end

end

@spec query!(conn, iodata, list, keyword()) :: Firebirdex.Result.t()
def query!(conn, statement, params \\ [], opts \\ []) do
case query(conn, statement, params, opts) do
{:ok, result} -> result
{:error, exception} -> raise exception
end
end

@spec prepare(conn(), iodata(), iodata(), keyword()) ::
{:ok, Firebirdex.Query.t()} | {:error, Firebirdex.Error.t()}
def prepare(conn, name, statement, opts \\ []) do
query = %Firebirdex.Query{name: name, statement: statement, ref: make_ref()}
DBConnection.prepare(conn, query, opts)
end

@spec prepare!(conn(), iodata(), iodata(), keyword()) :: Firebirdex.Query.t()
def prepare!(conn, name, statement, opts \\ []) do
query = %Firebirdex.Query{name: name, statement: statement, ref: make_ref()}
DBConnection.prepare!(conn, query, opts)
end

@spec prepare_execute(conn, iodata, iodata, list, keyword()) ::
{:ok, Firebirdex.Query.t(), Firebirdex.Result.t()} | {:error, Firebirdex.Error.t()}
def prepare_execute(conn, name, statement, params \\ [], opts \\ [])
when is_binary(statement) or is_list(statement) do
query = %Firebirdex.Query{name: name, statement: statement, ref: make_ref()}
DBConnection.prepare_execute(conn, query, params, opts)
end

@spec prepare_execute!(conn, iodata, iodata, list, keyword()) ::
{Firebirdex.Query.t(), Firebirdex.Result.t()}
def prepare_execute!(conn, name, statement, params \\ [], opts \\ [])
when is_binary(statement) or is_list(statement) do
query = %Firebirdex.Query{name: name, statement: statement, ref: make_ref()}
DBConnection.prepare_execute!(conn, query, params, opts)
end

@spec execute(conn(), Firebirdex.Query.t(), list(), keyword()) ::
{:ok, Firebirdex.Query.t(), Firebirdex.Result.t()} | {:error, Firebirdex.Error.t()}
defdelegate execute(conn, query, params \\ [], opts \\ []), to: DBConnection

@spec execute!(conn(), Firebirdex.Query.t(), list(), keyword()) :: Firebirdex.Result.t()
defdelegate execute!(conn, query, params \\ [], opts \\ []), to: DBConnection

@spec close(conn(), Firebirdex.Query.t(), keyword()) :: :ok
def close(conn, %Firebirdex.Query{} = query, opts \\ []) do
case DBConnection.close(conn, query, opts) do
{:ok, _} ->
:ok

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

@spec transaction(conn, (DBConnection.t() -> result), keyword()) ::
{:ok, result} | {:error, any}
when result: var
defdelegate transaction(conn, fun, opts \\ []), to: DBConnection

@spec rollback(DBConnection.t(), any()) :: no_return()
defdelegate rollback(conn, reason), to: DBConnection

@spec child_spec(keyword()) :: Supervisor.child_spec()
def child_spec(opts) do
DBConnection.child_spec(Firebirdex.Protocol, opts)
end

end
11 changes: 11 additions & 0 deletions lib/firebirdex/error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Firebirdex.Error do
defexception [
:message,
:statement
]

@type t :: %__MODULE__{
message: String.t(),
statement: iodata() | nil
}
end
113 changes: 113 additions & 0 deletions lib/firebirdex/protocol.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
defmodule Firebirdex.Protocol do
@moduledoc false
require Record
use DBConnection
Record.defrecord :conn, Record.extract(:conn, from_lib: "efirebirdsql/include/efirebirdsql.hrl")
Record.defrecord :stmt, Record.extract(:stmt, from_lib: "efirebirdsql/include/efirebirdsql.hrl")

alias Firebirdex.{Query, Result}

defstruct [
:conn,
transaction_status: :idle
]

@impl true
def connect(opts) do
hostname = to_charlist(opts[:hostname])
username = to_charlist(opts[:username])
password = to_charlist(opts[:password])
database = to_charlist(opts[:database])
case :efirebirdsql_protocol.connect(hostname, username, password, database, opts) do
{:ok, conn} ->
{:ok, conn} = :efirebirdsql_protocol.begin_transaction(false, conn)
{:ok, %__MODULE__{conn: conn}}
{:error, message, _conn} ->
{:error, %Firebirdex.Error{message: message}}
end
end

@impl true
def disconnect(_reason, state) do
:efirebirdsql_protocol.close(state.conn)
:ok
end

@impl true
def ping(state) do
# TODO
{:ok, state}
end

@impl true
def checkout(state) do
{:ok, state}
end

@impl true
def checkin(state) do
{:ok, state}
end

@impl true
def handle_prepare(%Query{} = query, _opts, state) do
{:ok, conn, stmt} = :efirebirdsql_protocol.allocate_statement(state.conn)
{:ok, conn, stmt} = :efirebirdsql_protocol.prepare_statement(
query.statement, conn, stmt)
{:ok, %Query{query | stmt: stmt}, %__MODULE__{state | conn: conn}}
end

@impl true
def handle_execute(%Query{} = query, params, _opts, state) do
{:ok, conn, stmt} = :efirebirdsql_protocol.execute(state.conn, query.stmt, params)
{:ok, rows, conn} = :efirebirdsql_protocol.fetchall(conn, stmt)
columns = :efirebirdsql_protocol.column_names(stmt)
{:ok, query, %Result{rows: rows, columns: columns}, %__MODULE__{state | conn: conn}}
end

@impl true
def handle_close(_query, _opts, state) do
{:ok, conn} = :efirebirdsql_protocol.close(state.conn)
{:ok, %__MODULE__{conn: conn}}
end

@impl true
def handle_status(_opts, status) do
{status.transaction_status, status}
end

@impl true
def handle_declare(query, _params, _opt, state) do
{:ok, query, query.stmt, state}
end

@impl true
def handle_begin(_opts, %{transaction_status: _status} = state) do
{:ok, conn} = :efirebirdsql_protocol.begin_transaction(false, state.conn)
{:ok, %__MODULE__{conn: conn}}
end

@impl true
def handle_commit(_opts, state) do
{:ok, conn} = :efirebirdsql_protocol.commit(state.conn)
{:ok, %__MODULE__{conn: conn}}
end

@impl true
def handle_rollback(_opts, state) do
{:ok, conn} = :efirebirdsql_protocol.rollback(state.conn)
{:ok, %__MODULE__{conn: conn}}
end

@impl true
def handle_fetch(_query, %Result{} = result, _opts, s) do
{:halt, result, s}
end

@impl true
def handle_deallocate(query, _cursor, _opts, state) do
{:ok, conn} = :efirebirdsql_protocol.free_statement(state.conn, query.stmt, :drop)
{:ok, %__MODULE__{state | conn: conn}}
end

end
45 changes: 45 additions & 0 deletions lib/firebirdex/query.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule Firebirdex.Query do
require Record
Record.defrecord :stmt, Record.extract(:stmt, from_lib: "efirebirdsql/include/efirebirdsql.hrl")

@type t :: %__MODULE__{
ref: reference() | nil,
name: iodata(),
statement: iodata(),
stmt: tuple()
}

defstruct name: "",
ref: nil,
stmt: nil,
statement: nil,
stmt: nil

defimpl DBConnection.Query do
def parse(query, _opts) do
query
end

def describe(query, _opts) do
query
end

def encode(%{stmt: nil} = query, _params, _opts) do
raise ArgumentError, "query #{inspect(query)} has not been prepared"
end

def encode(_query, params, _opts) do
params
end

def decode(_query, result, _opts) do
result
end
end

defimpl String.Chars do
def to_string(%{statement: statement}) do
IO.iodata_to_binary(statement)
end
end
end
11 changes: 11 additions & 0 deletions lib/firebirdex/result.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Firebirdex.Result do
@type t :: %__MODULE__{
columns: [String.t()] | nil,
rows: [[term()]] | nil
}

defstruct [
:columns,
:rows
]
end
42 changes: 42 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Firebirdex.Mixfile do
use Mix.Project

@version "0.0.1"

def project() do
[
app: :firebirdex,
version: @version,
elixir: "~> 1.4",
name: "Firebirdex",
description: "Firebird driver for Elixir",
source_url: "https://github.com/nakagami/firebirdex",
package: package(),
deps: deps()
]
end

def application() do
[
extra_applications: [:logger],
]
end

defp package do
[
maintainers: ["Hajime Nakagami"],
licenses: ["Apache 2.0"],
links: %{"Github" => "https://github.com/nakagami/firebirdex"}
]
end

defp deps() do
[
{:db_connection, "~> 2.0"},
{:decimal, "~> 1.6"},
{:efirebirdsql, "~> 0.5.2"},
]
end

end

6 changes: 6 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%{
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"db_connection": {:hex, :db_connection, "2.0.2", "440c05518b0bdca0469dafaf45403597430448c1281def14ef9ccaa41833ea1e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"},
"efirebirdsql": {:hex, :efirebirdsql, "0.5.2", "53dbc9f4fe332d6daa8a972ec7ec89b2947ccc6107762d22aa4b19ef94827436", [:rebar3], [], "hexpm"},
}
12 changes: 12 additions & 0 deletions test/firebirdex_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule FirebirdexTest do
use ExUnit.Case, async: true

@opts TestHelpers.opts()

describe "connect" do
opts = @opts
{:ok, conn} = Firebirdex.start_link(opts)
{:ok, %Firebirdex.Result{}} = Firebirdex.query(conn, "SELECT 1 AS C FROM RDB$RELATIONS", [])
end

end
16 changes: 16 additions & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ExUnit.start()

defmodule TestHelpers do
def opts() do
database = :lists.flatten(
:io_lib.format("/tmp/~p.fdb", [:erlang.system_time()]))
[
hostname: "localhost",
username: "sysdba",
password: "masterkey",
database: database,
createdb: true,
show_sensitive_data_on_connection_error: true
]
end
end

0 comments on commit b1a3c16

Please sign in to comment.