diff --git a/README.md b/README.md index 2770de4..1181432 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ Yesql is an Elixir library for _using_ SQL. - ## Rationale You're writing Elixir You need to write some SQL. diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..3769fe7 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,19 @@ +use Mix.Config + +case Mix.env() do + :test -> + config :yesql, ecto_repos: [YesqlTest.Repo] + + config :yesql, YesqlTest.Repo, adapter: Ecto.Adapters.Postgres + + config :yesql, YesqlTest.Repo, + username: "postgres", + password: "postgres", + database: "yesql_test", + hostname: "localhost" + + config :logger, level: :info + + _ -> + :ok +end diff --git a/lib/yesql.ex b/lib/yesql.ex index 8a43971..0dfface 100644 --- a/lib/yesql.ex +++ b/lib/yesql.ex @@ -1,11 +1,24 @@ defmodule Yesql do @moduledoc """ - Documentation for Yesql. + + defmodule Query do + use Yesql, driver: Postgrex, conn: MyApp.ConnectionPool + + Yesql.defquery("some/where/select_users_by_country.sql") + end + + Query.users_by_country(country_code: "gbr") + # => {:ok, [%{name: "Louis", country_code: "gbr"}]} + + ## Supported drivers + + - `Postgrex` + - `Ecto`, for which `conn` is an Ecto repo. """ alias __MODULE__.{NoDriver, UnknownDriver, MissingParam} - @supported_drivers [Postgrex] + @supported_drivers [Postgrex, Ecto] defmacro __using__(opts) do quote bind_quoted: binding() do @@ -90,6 +103,12 @@ defmodule Yesql do end end + if Code.ensure_compiled?(Ecto) do + defp exec_for_driver(repo, Ecto, sql, param_list) do + Ecto.Adapters.SQL.query(repo, sql, param_list) + end + end + defp exec_for_driver(_, driver, _, _) do raise UnknownDriver.exception(driver) end diff --git a/lib/yesql/exceptions.ex b/lib/yesql/exceptions.ex index bce9103..abfaa87 100644 --- a/lib/yesql/exceptions.ex +++ b/lib/yesql/exceptions.ex @@ -6,6 +6,7 @@ defmodule Yesql.MissingParam do msg = """ Required parameter `:#{param}` not given """ + %__MODULE__{message: msg} end end @@ -18,6 +19,7 @@ defmodule Yesql.UnknownDriver do msg = """ Unknown database driver #{driver} """ + %__MODULE__{message: msg} end end diff --git a/mix.exs b/mix.exs index 3a41916..d881ff3 100644 --- a/mix.exs +++ b/mix.exs @@ -4,9 +4,10 @@ defmodule Yesql.Mixfile do def project do [ app: :yesql, - version: "0.2.0", + version: "0.3.0", elixir: "~> 1.5", start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(Mix.env()), deps: deps(), name: "Yesql", description: "Using plain old SQL to query databases", @@ -20,17 +21,32 @@ defmodule Yesql.Mixfile do end def application do - [extra_applications: []] + case Mix.env() do + :test -> + [ + mod: {YesqlTest.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + + _ -> + [extra_applications: []] + end end + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + defp deps do [ # Postgresql driver {:postgrex, "~> 0.12", optional: true}, + # Database abstraction + {:ecto, "~> 2.0", optional: true}, + # Automatic testing tool {:mix_test_watch, ">= 0.0.0", only: :dev}, # Documentation generator - {:ex_doc, "~> 0.18.0", only: :dev} + {:ex_doc, "~> 0.18", only: :dev} ] end end diff --git a/mix.lock b/mix.lock index 9b559f0..2ebbacb 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,16 @@ %{ - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"}, - "mix_test_watch": {:hex, :mix_test_watch, "0.5.0", "2c322d119a4795c3431380fca2bca5afa4dc07324bd3c0b9f6b2efbdd99f5ed3", [], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, + "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.0", "e22b6434373b4870ea77b24df069dbac7002c1f483615e9ebfc0c37497e1c75c", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"}, + "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"}, + "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "mix_test_watch": {:hex, :mix_test_watch, "0.8.0", "acf97da2abc66532e7dc1aa66a5d6c9fc4442d7992d5d7eb4faeaeb964c2580e", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, } diff --git a/test/support/support.ex b/test/support/support.ex new file mode 100644 index 0000000..240f31c --- /dev/null +++ b/test/support/support.ex @@ -0,0 +1,13 @@ +defmodule YesqlTest.Application do + use Application + + def start(_type, _args) do + Application.ensure_all_started(:ecto) + Application.ensure_all_started(:postgrex) + Supervisor.start_link([YesqlTest.Repo], strategy: :one_for_one, name: Blondie.Supervisor) + end +end + +defmodule YesqlTest.Repo do + use Ecto.Repo, otp_app: :yesql +end diff --git a/test/yesql_test.exs b/test/yesql_test.exs index a610100..e773cb5 100644 --- a/test/yesql_test.exs +++ b/test/yesql_test.exs @@ -3,6 +3,25 @@ defmodule YesqlTest do doctest Yesql import TestHelper + defmodule Query do + use Yesql, driver: Postgrex + + Yesql.defquery("test/sql/select_older_cats.sql") + Yesql.defquery("test/sql/insert_cat.sql") + end + + defmodule QueryConn do + use Yesql, driver: Postgrex, conn: YesqlTest.Postgrex + Yesql.defquery("test/sql/select_older_cats.sql") + Yesql.defquery("test/sql/insert_cat.sql") + end + + defmodule QueryEcto do + use Yesql, driver: Ecto, conn: YesqlTest.Repo + Yesql.defquery("test/sql/select_older_cats.sql") + Yesql.defquery("test/sql/insert_cat.sql") + end + setup_all [:new_postgrex_connection, :create_cats_postgres_table] describe "parse/1" do @@ -112,31 +131,28 @@ defmodule YesqlTest do describe "defquery/2" do setup [:truncate_postgres_cats] - defmodule Query do - use Yesql, driver: Postgrex - - Yesql.defquery("test/sql/select_older_cats.sql") - Yesql.defquery("test/sql/insert_cat.sql") - end - test "query function is created" do refute function_exported?(Query, :select_older_cats, 1) assert function_exported?(Query, :select_older_cats, 2) + + # The /1 arity function is called because conn isn't needed. + assert function_exported?(QueryConn, :select_older_cats, 1) + assert function_exported?(QueryConn, :select_older_cats, 2) end test "throws if map argument missing" do assert_raise Yesql.MissingParam, "Required parameter `:age` not given\n", fn -> - Query.select_older_cats(nil, %{}) + QueryConn.select_older_cats(%{}) end end test "throws if keyword argument missing" do assert_raise Yesql.MissingParam, "Required parameter `:age` not given\n", fn -> - Query.select_older_cats(nil, []) + QueryConn.select_older_cats(nil, []) end end - test "query exec", %{postgrex: conn} do + test "query exec with explicit conn", %{postgrex: conn} do assert Query.select_older_cats(conn, age: 5) == {:ok, []} assert Query.insert_cat(conn, age: 50) == {:ok, []} assert Query.select_older_cats(conn, age: 5) == {:ok, [%{age: 50, name: nil}]} @@ -150,38 +166,8 @@ defmodule YesqlTest do assert Query.select_older_cats(conn, age: 5) == {:ok, [%{age: 10, name: nil}, %{age: 50, name: nil}]} end - end - - # TODO - # describe "defquery/2 with driver passed" - - describe "defquery/2 with conn" do - setup [:truncate_postgres_cats] - - defmodule QueryConn do - use Yesql, driver: Postgrex, conn: YesqlTest.Postgrex - Yesql.defquery("test/sql/select_older_cats.sql") - Yesql.defquery("test/sql/insert_cat.sql") - end - - test "query function is created" do - assert function_exported?(QueryConn, :select_older_cats, 1) - assert function_exported?(QueryConn, :select_older_cats, 2) - end - test "throws if map argument missing" do - assert_raise Yesql.MissingParam, "Required parameter `:age` not given\n", fn -> - QueryConn.select_older_cats(%{}) - end - end - - test "throws if keyword argument missing" do - assert_raise Yesql.MissingParam, "Required parameter `:age` not given\n", fn -> - QueryConn.select_older_cats([]) - end - end - - test "query exec" do + test "query exec with implicit conn" do assert QueryConn.select_older_cats(age: 5) == {:ok, []} assert QueryConn.insert_cat(age: 50) == {:ok, []} assert QueryConn.select_older_cats(age: 5) == {:ok, [%{age: 50, name: nil}]} @@ -195,5 +181,11 @@ defmodule YesqlTest do assert QueryConn.select_older_cats(age: 5) == {:ok, [%{age: 10, name: nil}, %{age: 50, name: nil}]} end + + test "query exec with Ecto driver" do + assert QueryEcto.select_older_cats(age: 5) == {:ok, []} + assert QueryEcto.insert_cat(age: 50) == {:ok, []} + assert QueryEcto.select_older_cats(age: 5) == {:ok, [%{age: 50, name: nil}]} + end end end