From 35367339b8d00d3ca2739dd2173e2fa3aaff0c0c Mon Sep 17 00:00:00 2001 From: Paul Engel Date: Fri, 14 Jul 2017 02:08:23 +0200 Subject: [PATCH 1/4] Outputting :limit and :offset option as special variable Being able to resolve query placeholder values based on three tuple output --- lib/sql_dust.ex | 38 ++++++++++++--------- lib/sql_dust/utils/scan_utils.ex | 25 +++----------- mix.lock | 26 +++++++-------- test/sql_dust_test.exs | 57 ++++++++++++-------------------- 4 files changed, 61 insertions(+), 85 deletions(-) diff --git a/lib/sql_dust.ex b/lib/sql_dust.ex index f4eca6e..b35c3eb 100644 --- a/lib/sql_dust.ex +++ b/lib/sql_dust.ex @@ -14,8 +14,7 @@ defmodule SqlDust do options = %{ select: ".*", adapter: :mysql, - initial_variables: options[:variables] || %{}, - variables: %{}, + variables: options[:variables] || %{}, unique: false } |> Map.merge(options) @@ -40,6 +39,19 @@ defmodule SqlDust do |> compose_sql end + def resolve_placeholders(placeholders, options) do + Enum.map(placeholders, fn(path) -> + case Regex.run(~r/option:(\w+)/, path) do + nil -> + String.split(path, ".") |> Enum.reduce(options.variables, fn(name, variables) -> + MapUtils.get(variables, name) + end) + [_match, key] -> + options[String.to_atom(key)] + end + end) + end + defp derive_select(options) do list = split_arguments(options[:select]) @@ -149,7 +161,7 @@ defmodule SqlDust do defp derive_limit(options) do if limit = MapUtils.get(options, :limit) do - options |> interpolate_option_variable(:limit, limit) + options |> put_option_placeholder(:limit, limit) else options end @@ -157,7 +169,7 @@ defmodule SqlDust do defp derive_offset(options) do if offset = MapUtils.get(options, :offset) do - options |> interpolate_option_variable(:offset, offset) + options |> put_option_placeholder(:offset, offset) else options end @@ -212,21 +224,15 @@ defmodule SqlDust do Map.put(options, key, prefix <> (conditions |> Enum.join(" AND "))) end - defp interpolate_option_variable(options, key, value) do - variables = options.variables + defp put_option_placeholder(options, key, value) do + interpolated_key = "option:" <> Atom.to_string(key) variables = - unless value == "?" do - option_variables = (options.variables[:_options_] || %{}) |> Map.put(key, value) - options.variables |> Map.put(:_options_, option_variables) - else - variables - end - - interpolated_key = " <<_options_." <> Atom.to_string(key) <> ">>" + options.variables + |> Map.put(interpolated_key, value) options - |> Map.put(key, (Atom.to_string(key) |> String.upcase) <> interpolated_key) + |> Map.put(key, (Atom.to_string(key) |> String.replace("_", " ") |> String.upcase) <> " <<" <> interpolated_key <> ">>") |> Map.put(:variables, variables) end @@ -248,6 +254,6 @@ defmodule SqlDust do |> List.flatten |> Enum.reject(&is_nil/1) |> Enum.join("\n") - |> interpolate_variables(options.variables, options.initial_variables) + |> interpolate_placeholders(options) end end diff --git a/lib/sql_dust/utils/scan_utils.ex b/lib/sql_dust/utils/scan_utils.ex index ba21522..4f34a66 100644 --- a/lib/sql_dust/utils/scan_utils.ex +++ b/lib/sql_dust/utils/scan_utils.ex @@ -1,7 +1,7 @@ defmodule SqlDust.ScanUtils do alias SqlDust.MapUtils - @variable_regex ~r/<<([\w\.]+)>>/ + @variable_regex ~r/<<((?:option:)?[\w\.]+)>>/ def split_arguments(sql) when is_list(sql) do sql @@ -126,11 +126,10 @@ defmodule SqlDust.ScanUtils do {String.replace(sql, "{#{index}}", pattern), index} end - def interpolate_variables(sql, variables, initial_variables) do + def interpolate_placeholders(sql, %{variables: variables}) do excluded = scan_strings(sql) sql = numerize_patterns(sql, excluded) - {sql, values, keys} = @variable_regex |> Regex.scan(sql) @@ -139,20 +138,7 @@ defmodule SqlDust.ScanUtils do MapUtils.get(variables, name) end) - anonymous_key = - Regex.match?(~r(__\d+__), key) || ( - String.contains?(key, "_options_") && ( - !(initial_variables - |> MapUtils.get(:_options_, %{}) - |> Map.keys - |> Enum.map(fn - (k) when is_atom(k) -> Atom.to_string(k) - (k) -> k - end) - |> Enum.member?(String.split(key, ".", parts: 3) |> Enum.at(1))) - ) - ) - + anonymous_key = Regex.match?(~r(__\d+__), key) sql = String.replace sql, match, "?", global: false values = [value | values] key = if anonymous_key, do: nil, else: key @@ -164,10 +150,7 @@ defmodule SqlDust.ScanUtils do keys = Enum.reverse(keys) sql = interpolate_patterns(sql, excluded) - include_keys = Enum.any?(Map.keys(initial_variables), fn(key) -> - key = if is_atom(key), do: Atom.to_string(key), else: key - !Regex.match?(~r(__\d+__), key) - end) + include_keys = (length(keys) > 0) && !Enum.any?(keys, &is_nil/1) if include_keys do {sql, values, keys} diff --git a/mix.lock b/mix.lock index 310b1d3..c250497 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,13 @@ -%{"benchfella": {:hex, :benchfella, "0.3.4", "41d2c017b361ece5225b5ba2e3b30ae53578c57c6ebc434417b4f1c2c94cf4f3", [:mix], []}, - "bunt": {:hex, :bunt, "0.1.5", "c378ea1698232597d3778e4b83234dcea4a60e7c28114b0fe53657a2c0d8885e", [:mix], []}, - "credo": {:hex, :credo, "0.3.13", "90d2d2deb9d376bb2a63f81126a320c3920ce65acb1294982ab49a8aacc7d89f", [:mix], [{:bunt, "~> 0.1.4", [hex: :bunt, optional: false]}]}, - "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []}, - "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []}, - "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]}, - "ex_doc": {:hex, :ex_doc, "0.11.4", "a064bdb720594c3745b94709b17ffb834fd858b4e0c1f48f37c0d92700759e02", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]}, - "exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], []}, - "exprof": {:hex, :exprof, "0.2.0", "b03f50d0d33e2f18c8e047d9188ba765dc32daba0b553ed717a98a78561d5eaf", [:mix], [{:exprintf, "~> 0.1", [hex: :exprintf, optional: false]}]}, - "inch_ex": {:hex, :inch_ex, "0.5.1", "c1c18966c935944cbb2d273796b36e44fab3c54fd59f906ff026a686205b4e14", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, - "inflex": {:hex, :inflex, "1.6.0", "2c0c9928cd865e3b47cfaaa15c3520dfdb8c6ba5829d036296deb766f2aba770", [:mix], []}, - "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, - "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}} +%{"benchfella": {:hex, :benchfella, "0.3.4", "41d2c017b361ece5225b5ba2e3b30ae53578c57c6ebc434417b4f1c2c94cf4f3", [:mix], [], "hexpm"}, + "bunt": {:hex, :bunt, "0.1.5", "c378ea1698232597d3778e4b83234dcea4a60e7c28114b0fe53657a2c0d8885e", [:mix], [], "hexpm"}, + "credo": {:hex, :credo, "0.3.13", "90d2d2deb9d376bb2a63f81126a320c3920ce65acb1294982ab49a8aacc7d89f", [:mix], [{:bunt, "~> 0.1.4", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], [], "hexpm"}, + "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.11.4", "a064bdb720594c3745b94709b17ffb834fd858b4e0c1f48f37c0d92700759e02", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, repo: "hexpm", optional: true]}], "hexpm"}, + "exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], [], "hexpm"}, + "exprof": {:hex, :exprof, "0.2.0", "b03f50d0d33e2f18c8e047d9188ba765dc32daba0b553ed717a98a78561d5eaf", [:mix], [{:exprintf, "~> 0.1", [hex: :exprintf, repo: "hexpm", optional: false]}], "hexpm"}, + "inch_ex": {:hex, :inch_ex, "0.5.1", "c1c18966c935944cbb2d273796b36e44fab3c54fd59f906ff026a686205b4e14", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "inflex": {:hex, :inflex, "1.6.0", "2c0c9928cd865e3b47cfaaa15c3520dfdb8c6ba5829d036296deb766f2aba770", [:mix], [], "hexpm"}, + "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}} diff --git a/test/sql_dust_test.exs b/test/sql_dust_test.exs index f56fc11..e3f98c0 100644 --- a/test/sql_dust_test.exs +++ b/test/sql_dust_test.exs @@ -595,30 +595,13 @@ defmodule SqlDustTest do limit: 20 } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - LIMIT ? - """, [20]} - end - - test "limiting the query result by passing the limit in variables[:_options_][:limit]" do - options = %{ - limit: "?", - variables: %{ - _options_: %{ - limit: 20 - } - } - } - assert SqlDust.from("users", options) == {""" SELECT `u`.* FROM `users` `u` LIMIT ? """, [20], - ~w(_options_.limit)} + ~w(option:limit)} end test "adding an offset to the query result" do @@ -632,28 +615,30 @@ defmodule SqlDustTest do FROM `users` `u` LIMIT ? OFFSET ? - """, [10, 20]} + """, + [10, 20], + ~w(option:limit option:offset)} end - test "adding an offset to the query result by passing the offset in variables[:_options_][:offset]" do + test "resolving placeholders" do + placeholders = + ~w(person.first_name person.last_name person.last_name option:limit option:offset) + options = %{ - limit: 10, - offset: "?", variables: %{ - _options_: %{ - offset: 20 - } - } + person: %{ + first_name: "Paul", + last_name: "Engel" + }, + awesome: true + }, + limit: 10, + offset: 20 } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - LIMIT ? - OFFSET ? - """, - [10, 20], - [nil, "_options_.offset"]} + assert SqlDust.resolve_placeholders(placeholders, options) == [ + "Paul", "Engel", "Engel", 10, 20 + ] end test "quoting SELECT statement aliases" do @@ -891,7 +876,9 @@ defmodule SqlDustTest do HAVING (`order.count` > 5) ORDER BY COUNT(DISTINCT `tags`.`id`) DESC LIMIT ? - """, [5]} + """, + [5], + ~w(option:limit)} end test "DirectiveRecord example 3" do From 1b72e3161c21006364cc3830316697000a620a03 Mon Sep 17 00:00:00 2001 From: Paul Engel Date: Mon, 17 Jul 2017 15:23:58 +0200 Subject: [PATCH 2/4] Being able to resolve placeholders falling back to an optional list of static values --- lib/sql_dust.ex | 20 +++++++++++++++++--- test/sql_dust_test.exs | 28 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/lib/sql_dust.ex b/lib/sql_dust.ex index b35c3eb..364587d 100644 --- a/lib/sql_dust.ex +++ b/lib/sql_dust.ex @@ -39,12 +39,26 @@ defmodule SqlDust do |> compose_sql end - def resolve_placeholders(placeholders, options) do - Enum.map(placeholders, fn(path) -> + def resolve_placeholders(placeholders, options, values \\ []) do + placeholders + |> Enum.with_index() + |> Enum.map(fn({path, index}) -> case Regex.run(~r/option:(\w+)/, path) do nil -> String.split(path, ".") |> Enum.reduce(options.variables, fn(name, variables) -> - MapUtils.get(variables, name) + if is_map(variables) do + case MapUtils.get(variables, name) do + nil -> + if Map.has_key?(variables, name) || Map.has_key?(variables, name |> String.to_atom()) do + nil + else + Enum.at(values, index) + end + value -> value + end + else + variables + end end) [_match, key] -> options[String.to_atom(key)] diff --git a/test/sql_dust_test.exs b/test/sql_dust_test.exs index e3f98c0..e15cdfb 100644 --- a/test/sql_dust_test.exs +++ b/test/sql_dust_test.exs @@ -622,7 +622,7 @@ defmodule SqlDustTest do test "resolving placeholders" do placeholders = - ~w(person.first_name person.last_name person.last_name option:limit option:offset) + ~w(person.first_name person.last_name person.last_name option:limit option:offset epic) options = %{ variables: %{ @@ -637,7 +637,31 @@ defmodule SqlDustTest do } assert SqlDust.resolve_placeholders(placeholders, options) == [ - "Paul", "Engel", "Engel", 10, 20 + "Paul", "Engel", "Engel", 10, 20, nil + ] + end + + test "resolving placeholders (with fallback to static values)" do + placeholders = + ~w(person.first_name person.last_name person.last_name awesome option:limit option:offset) + + options = %{ + variables: %{ + person: %{ + last_name: "Engel" + }, + awesome: nil + }, + limit: 10, + offset: 20 + } + + values = [ + "Ken", "Engel", "Engel", true, 10, 20 + ] + + assert SqlDust.resolve_placeholders(placeholders, options, values) == [ + "Ken", "Engel", "Engel", nil, 10, 20 ] end From 4c96585bbbfb71bcd7d973ead399b85984c508c8 Mon Sep 17 00:00:00 2001 From: Paul Engel Date: Tue, 1 Aug 2017 11:35:52 +0200 Subject: [PATCH 3/4] Forcing to install rebar before Travis-CI build --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 97f7dca..265d7a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ before_install: - unzip -d elixir v1.4.2.zip before_script: - export PATH=`pwd`/elixir/bin:$PATH + - mix local.rebar --force - mix local.hex --force - mix deps.get --only test script: mix test From a0914a6d5994c13038d16cf70bf2038596737bca Mon Sep 17 00:00:00 2001 From: Paul Engel Date: Tue, 1 Aug 2017 11:42:32 +0200 Subject: [PATCH 4/4] Bumped to credo 0.8.4 --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index 729e7c8..e58460a 100644 --- a/mix.exs +++ b/mix.exs @@ -24,7 +24,7 @@ defmodule SqlDust.Mixfile do {:benchfella, "~> 0.3.0", only: :dev}, {:earmark, "~> 0.1", only: :dev}, {:ex_doc, "~> 0.11", only: :dev}, - {:credo, "~> 0.2", only: [:dev, :test]}, + {:credo, "~> 0.8", only: [:dev, :test]}, {:inch_ex, ">= 0.0.0", only: :docs} ] end diff --git a/mix.lock b/mix.lock index c250497..052433d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{"benchfella": {:hex, :benchfella, "0.3.4", "41d2c017b361ece5225b5ba2e3b30ae53578c57c6ebc434417b4f1c2c94cf4f3", [:mix], [], "hexpm"}, - "bunt": {:hex, :bunt, "0.1.5", "c378ea1698232597d3778e4b83234dcea4a60e7c28114b0fe53657a2c0d8885e", [:mix], [], "hexpm"}, - "credo": {:hex, :credo, "0.3.13", "90d2d2deb9d376bb2a63f81126a320c3920ce65acb1294982ab49a8aacc7d89f", [:mix], [{:bunt, "~> 0.1.4", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, + "credo": {:hex, :credo, "0.8.4", "4e50acac058cf6292d6066e5b0d03da5e1483702e1ccde39abba385c9f03ead4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},