From cb3fe49aafc6e2c6c09876e169a84fe8f4277cf9 Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:21:59 +0200 Subject: [PATCH 1/6] Update deps --- mix.exs | 14 ++++++-------- mix.lock | 36 +++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/mix.exs b/mix.exs index 729e7c8..53a4e1e 100644 --- a/mix.exs +++ b/mix.exs @@ -18,14 +18,12 @@ defmodule SqlDust.Mixfile do defp deps do [ - {:inflex, "~> 1.6.0"}, - {:ecto, ">= 1.1.0", optional: true}, - {:exprof, "~> 0.2.0", only: :dev}, - {:benchfella, "~> 0.3.0", only: :dev}, - {:earmark, "~> 0.1", only: :dev}, - {:ex_doc, "~> 0.11", only: :dev}, - {:credo, "~> 0.2", only: [:dev, :test]}, - {:inch_ex, ">= 0.0.0", only: :docs} + {:inflex, "~> 2.0"}, + {:ecto, "~> 3.7", optional: true}, + {:exprof, "~> 0.2", only: :dev}, + {:benchfella, "~> 0.3", only: :dev}, + {:ex_doc, "~> 0.25", only: :dev}, + {:credo, "~> 1.5", only: [:dev, :test]}, ] end diff --git a/mix.lock b/mix.lock index 310b1d3..37ac987 100644 --- a/mix.lock +++ b/mix.lock @@ -1,13 +1,23 @@ -%{"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", "23616f774db00fc896ce249380f2c0053161d5803775c7284a156bc58a5a3146"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], [], "hexpm", "c86afb8d22a5aa8315afd4257c7512011c0c9a48b0fea43af7612836b958098b"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, + "ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"}, + "ex_doc": {:hex, :ex_doc, "0.25.3", "3edf6a0d70a39d2eafde030b8895501b1c93692effcbd21347296c18e47618ce", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "9ebebc2169ec732a38e9e779fd0418c9189b3ca93f4a676c961be6c1527913f5"}, + "exprintf": {:hex, :exprintf, "0.2.1", "b7e895dfb00520cfb7fc1671303b63b37dc3897c59be7cbf1ae62f766a8a0314", [:mix], [], "hexpm", "20a0e8c880be90e56a77fcc82533c5d60c643915c7ce0cc8aa1e06ed6001da28"}, + "exprof": {:hex, :exprof, "0.2.0", "b03f50d0d33e2f18c8e047d9188ba765dc32daba0b553ed717a98a78561d5eaf", [:mix], [{:exprintf, "~> 0.1", [hex: :exprintf, repo: "hexpm", optional: false]}], "hexpm", "2b3b8c623873172a6c7ba1707981f51feea6b6edbabd5347752030803ad0c954"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "inch_ex": {:hex, :inch_ex, "0.5.1", "c1c18966c935944cbb2d273796b36e44fab3c54fd59f906ff026a686205b4e14", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "4d0aaefa4928fdc4758118a37dccb5b90805559ada3f652ca157f66ea268ea20"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm", "519bc209e4433961284174c497c8524c001e285b79bdf80212b47a1f898084cc"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm", "8f7168911120e13419e086e78d20e4d1a6776f1eee2411ac9f790af10813389f"}, + "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, +} From 80d579e99a520100f5d40727be0b73d8cbc5e77a Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:22:14 +0200 Subject: [PATCH 2/6] Fix compilation warnings --- test/sql_dust/query_test.exs | 20 ++++++------- test/sql_dust_test.exs | 58 ++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/test/sql_dust/query_test.exs b/test/sql_dust/query_test.exs index ecf5cdd..20b7132 100644 --- a/test/sql_dust/query_test.exs +++ b/test/sql_dust/query_test.exs @@ -326,8 +326,8 @@ defmodule SqlDust.QueryTest do test "schema returns SqlDust containing :schema option" do query_dust = schema(%{ - "users": %{ - "skills": %{ + users: %{ + skills: %{ cardinality: :has_and_belongs_to_many } } @@ -347,8 +347,8 @@ defmodule SqlDust.QueryTest do test "schema merges argument to existing :schema option" do query_dust = schema( %{ - "users": %{ - "skills": %{ + users: %{ + skills: %{ cardinality: :has_and_belongs_to_many } } @@ -356,12 +356,12 @@ defmodule SqlDust.QueryTest do ) |> schema( %{ - "users": %{ - "skills": %{ - "primary_key": "identifier" + users: %{ + skills: %{ + primary_key: "identifier" } }, - "relations": %{ + relations: %{ table_name: "users" } } @@ -454,8 +454,8 @@ defmodule SqlDust.QueryTest do |> limit(20) |> schema( %{ - users: %{"table_name": "people"}, - companies: %{"address": %{cardinality: :has_one}} + users: %{table_name: "people"}, + companies: %{address: %{cardinality: :has_one}} } ) |> unique diff --git a/test/sql_dust_test.exs b/test/sql_dust_test.exs index 01a05c3..aedb932 100644 --- a/test/sql_dust_test.exs +++ b/test/sql_dust_test.exs @@ -192,11 +192,11 @@ defmodule SqlDustTest do } schema = %{ - "departments": %{ + departments: %{ table_name: "super department.store" }, - "users": %{ - "department": %{ + users: %{ + department: %{ foreign_key: "insane weird.stuff" } } @@ -269,8 +269,8 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } schema = %{ - "users": %{ - "skills": %{ + users: %{ + skills: %{ cardinality: :has_and_belongs_to_many } } @@ -294,7 +294,7 @@ defmodule SqlDustTest do group_by: "id" } schema = %{ - "products": %{ + products: %{ current_price: %{ cardinality: :has_one, resource: "prices" @@ -319,7 +319,7 @@ defmodule SqlDustTest do join_on: "current_price.latest = 1" } schema = %{ - "products": %{ + products: %{ current_price: %{ cardinality: :has_one, resource: "prices" @@ -342,7 +342,7 @@ defmodule SqlDustTest do select: "id, name, current_price.amount" } schema = %{ - "products": %{ + products: %{ current_price: %{ cardinality: :has_one, resource: "prices", @@ -365,11 +365,11 @@ defmodule SqlDustTest do options = %{ select: "id, name, current_statistic.amount", variables: %{ - "scope": "awesome_scope" + scope: "awesome_scope" } } schema = %{ - "products": %{ + products: %{ current_statistic: %{ cardinality: :has_one, resource: "statistics", @@ -397,7 +397,7 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(company.tags.name)" } schema = %{ - "companies": %{ + companies: %{ tags: %{ cardinality: :has_and_belongs_to_many } @@ -475,8 +475,8 @@ defmodule SqlDustTest do test "overriding the resource table name" do schema = %{ - "resellers": %{ - "table_name": "companies" + resellers: %{ + table_name: "companies" } } @@ -491,7 +491,7 @@ defmodule SqlDustTest do select: ["id", "description", "CONCAT(assignee.first_name, ' ', assignee.last_name)"] } schema = %{ - "issues": %{ + issues: %{ assignee: %{resource: "users"} } } @@ -511,7 +511,7 @@ defmodule SqlDustTest do select: ["id", "description", "CONCAT(assignee.first_name, ' ', assignee.last_name)"] } schema = %{ - "issues": %{ + issues: %{ assignee: %{table_name: "users"} } } @@ -531,8 +531,8 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } schema = %{ - "users": %{ - "skills": %{ + users: %{ + skills: %{ cardinality: :has_and_belongs_to_many, bridge_table: "skill_set", foreign_key: "person_id" @@ -557,8 +557,8 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } schema = %{ - "users": %{ - "skills": %{ + users: %{ + skills: %{ cardinality: :has_and_belongs_to_many, bridge_table: "test skill_set.awesome", foreign_key: "strange.person_id" @@ -789,12 +789,12 @@ defmodule SqlDustTest do } schema = %{ - "organizations": %{ - "addresses": %{ + organizations: %{ + addresses: %{ foreign_key: "sql dust.is.cool" } }, - "addresses": %{ + addresses: %{ table_name: "some address.table" } } @@ -817,7 +817,7 @@ defmodule SqlDustTest do } schema = %{ - "users": %{ + users: %{ organization: %{ cardinality: :has_one } @@ -841,13 +841,13 @@ defmodule SqlDustTest do } schema = %{ - "users": %{ + users: %{ organization: %{ cardinality: :has_one, foreign_key: "user.organization column" } }, - "organizations": %{ + organizations: %{ table_name: "organization hallo.test" } } @@ -869,7 +869,7 @@ defmodule SqlDustTest do } schema = %{ - "users": %{ + users: %{ skills: %{ cardinality: :has_and_belongs_to_many } @@ -927,9 +927,9 @@ defmodule SqlDustTest do where: "tags.name LIKE '%gifts%'" } schema = %{ - "customers": %{ + customers: %{ tags: %{ - "cardinality": :has_and_belongs_to_many + cardinality: :has_and_belongs_to_many } } } @@ -952,7 +952,7 @@ defmodule SqlDustTest do schema = %{ customers: %{ tags: %{ - "cardinality": :has_and_belongs_to_many + cardinality: :has_and_belongs_to_many } } } From 9c5ec4c793d77b904f436e24a50d510923782b3a Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:23:05 +0200 Subject: [PATCH 3/6] Add formatter.exs --- .formatter.exs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .formatter.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] From c4dfb41eeec3c122a76a7c657e9f007e539bcf43 Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:27:24 +0200 Subject: [PATCH 4/6] Mix format + fix compilation warnings --- lib/ecto/sql_dust.ex | 66 +- lib/sql_dust.ex | 177 +++--- lib/sql_dust/utils/compose_utils.ex | 182 ++++-- lib/sql_dust/utils/join_utils.ex | 143 +++-- lib/sql_dust/utils/map_utils.ex | 24 +- lib/sql_dust/utils/path_utils.ex | 92 +-- lib/sql_dust/utils/scan_utils.ex | 105 ++-- lib/sql_dust/utils/schema_utils.ex | 87 +-- mix.exs | 22 +- test/ecto/sql_dust_test.exs | 91 +-- test/sql_dust/map_utils_test.exs | 6 +- test/sql_dust/query_test.exs | 429 +++++++------ test/sql_dust_test.exs | 923 +++++++++++++++------------- 13 files changed, 1336 insertions(+), 1011 deletions(-) diff --git a/lib/ecto/sql_dust.ex b/lib/ecto/sql_dust.ex index 37c91a3..2ad5a94 100644 --- a/lib/ecto/sql_dust.ex +++ b/lib/ecto/sql_dust.ex @@ -6,9 +6,9 @@ defmodule Ecto.SqlDust do derived_schema = derive_schema(model) options - |> __from__(resource) - |> adapter(:postgres) - |> schema(derived_schema) + |> __from__(resource) + |> adapter(:postgres) + |> schema(derived_schema) end defp parse(options) do @@ -30,20 +30,26 @@ defmodule Ecto.SqlDust do source = model.__schema__(:source) associations = model.__schema__(:associations) - schema = schema - |> Map.put(source, Enum.reduce(associations, %{name: source, table_name: source}, fn(association, map) -> - reflection = model.__schema__(:association, association) - Map.put(map, association, derive_association(reflection)) - end)) + schema = + schema + |> Map.put( + source, + Enum.reduce(associations, %{name: source, table_name: source}, fn association, map -> + reflection = model.__schema__(:association, association) + Map.put(map, association, derive_association(reflection)) + end) + ) - schema = associations - |> Enum.map(fn(association) -> + schema = + associations + |> Enum.map(fn association -> model.__schema__(:association, association).queryable end) - |> Enum.uniq - |> Enum.reduce(schema, fn(model, schema) -> + |> Enum.uniq() + |> Enum.reduce(schema, fn model, schema -> model_source = model.__schema__(:source) - if (source == model_source) || Map.has_key?(schema, model_source) do + + if source == model_source || Map.has_key?(schema, model_source) do schema else derive_schema(schema, model) @@ -54,20 +60,28 @@ defmodule Ecto.SqlDust do end defp derive_association(reflection) do - cardinality = case reflection.__struct__ do - Ecto.Association.BelongsTo -> :belongs_to - Ecto.Association.Has -> - case reflection.cardinality do - :one -> :has_one - :many -> :has_many - end - Ecto.Association.ManyToMany -> :has_and_belongs_to_many - end + cardinality = + case reflection.__struct__ do + Ecto.Association.BelongsTo -> + :belongs_to + + Ecto.Association.Has -> + case reflection.cardinality do + :one -> :has_one + :many -> :has_many + end + + Ecto.Association.ManyToMany -> + :has_and_belongs_to_many + end - Map.merge(%{ - cardinality: cardinality, - resource: reflection.related.__schema__(:source) - }, derive_association(cardinality, reflection)) + Map.merge( + %{ + cardinality: cardinality, + resource: reflection.related.__schema__(:source) + }, + derive_association(cardinality, reflection) + ) end defp derive_association(:belongs_to, reflection) do diff --git a/lib/sql_dust.ex b/lib/sql_dust.ex index f4eca6e..ae92cf5 100644 --- a/lib/sql_dust.ex +++ b/lib/sql_dust.ex @@ -5,19 +5,33 @@ defmodule SqlDust do import SqlDust.JoinUtils alias SqlDust.MapUtils - defstruct [:select, :from, :join_on, :where, :group_by, :order_by, :limit, :offset, :unique, :schema, :variables, :adapter] + defstruct [ + :select, + :from, + :join_on, + :where, + :group_by, + :order_by, + :limit, + :offset, + :unique, + :schema, + :variables, + :adapter + ] @moduledoc """ SqlDust is a module that generates SQL queries as intuitively as possible. """ def from(resource, options \\ %{}, schema \\ %{}) do - options = %{ - select: ".*", - adapter: :mysql, - initial_variables: options[:variables] || %{}, - variables: %{}, - unique: false - } + options = + %{ + select: ".*", + adapter: :mysql, + initial_variables: options[:variables] || %{}, + variables: %{}, + unique: false + } |> Map.merge(options) |> Map.merge(%{ aliases: [], @@ -26,56 +40,66 @@ defmodule SqlDust do }) options - |> Map.put(:resource, resource_schema(resource, options)) - |> derive_select - |> derive_from - |> derive_join_on - |> derive_where - |> derive_group_by - |> derive_order_by - |> derive_limit - |> derive_offset - |> derive_joins - |> ensure_unique_records - |> compose_sql + |> Map.put(:resource, resource_schema(resource, options)) + |> derive_select + |> derive_from + |> derive_join_on + |> derive_where + |> derive_group_by + |> derive_order_by + |> derive_limit + |> derive_offset + |> derive_joins + |> ensure_unique_records + |> compose_sql end defp derive_select(options) do list = split_arguments(options[:select]) - {select, options} = list + {select, options} = + list |> prepend_path_aliases(options) - options = Map.put(options, :aliases, Enum.reject(options.aliases, fn(sql_alias) -> - Enum.member?(list, sql_alias <> " AS " <> sql_alias) - end)) + options = + Map.put( + options, + :aliases, + Enum.reject(options.aliases, fn sql_alias -> + Enum.member?(list, sql_alias <> " AS " <> sql_alias) + end) + ) - prefix = if String.length(Enum.join(select, ", ")) > 45 do - "\n " - else - " " - end + prefix = + if String.length(Enum.join(select, ", ")) > 45 do + "\n " + else + " " + end - select = select - |> Enum.map(fn(sql) -> "#{prefix}#{sql}" end) + select = + select + |> Enum.map(fn sql -> "#{prefix}#{sql}" end) |> Enum.join(",") - Map.put options, :select, "SELECT#{select}" + Map.put(options, :select, "SELECT#{select}") end defp derive_from(options) do - from = "#{quote_alias(options.resource.table_name, options)} #{derive_quoted_path_alias("", options)}" + from = + "#{quote_alias(options.resource.table_name, options)} #{derive_quoted_path_alias("", options)}" - Map.put options, :from, "FROM #{from}" + Map.put(options, :from, "FROM #{from}") end defp derive_joins(options) do - joins = options.paths - |> Enum.uniq - |> Enum.map(fn(path) -> derive_joins(path, options) end) - |> List.flatten + joins = + options.paths + |> Enum.uniq() + |> Enum.map(fn path -> derive_joins(path, options) end) + |> List.flatten() - Map.put options, :joins, joins + Map.put(options, :joins, joins) end defp derive_join_on(options) do @@ -89,6 +113,7 @@ defmodule SqlDust do defp derive_where(%{where: ""} = options), do: Map.put(options, :where, []) defp derive_where(%{where: [""]} = options), do: Map.put(options, :where, []) defp derive_where(%{where: []} = options), do: options + defp derive_where(%{where: where} = options) do where = where @@ -98,9 +123,10 @@ defmodule SqlDust do {having, where} = where - |> Enum.partition(fn([sql | _]) -> + |> Enum.split_with(fn [sql | _] -> sql = sanitize_sql(sql) - Enum.any?(options.aliases, fn(sql_alias) -> + + Enum.any?(options.aliases, fn sql_alias -> String.match?(sql, ~r/(^|[^\.\w])#{sql_alias}([^\.\w]|$)/) end) end) @@ -111,13 +137,15 @@ defmodule SqlDust do options end + defp derive_where(options), do: options + defp sanitize_where(conditions) do conditions - |> Enum.filter(fn(condition) -> + |> Enum.filter(fn condition -> case condition do condition when is_binary(condition) -> String.trim(condition) != "" - [""|_tail] -> false + ["" | _tail] -> false _ -> true end end) @@ -125,7 +153,8 @@ defmodule SqlDust do defp derive_group_by(options) do if group_by = MapUtils.get(options, :group_by) do - {group_by, options} = group_by + {group_by, options} = + group_by |> split_arguments |> prepend_path_aliases(options) @@ -137,7 +166,8 @@ defmodule SqlDust do defp derive_order_by(options) do if order_by = MapUtils.get(options, :order_by) do - {order_by, options} = order_by + {order_by, options} = + order_by |> split_arguments |> prepend_path_aliases(options) @@ -164,7 +194,8 @@ defmodule SqlDust do end defp ensure_unique_records(options) do - if options.unique && !MapUtils.get(options, :group_by) && Enum.any?(options.joins, fn(x) -> elem(x, 0) != :belongs_to end) do + if options.unique && !MapUtils.get(options, :group_by) && + Enum.any?(options.joins, fn x -> elem(x, 0) != :belongs_to end) do options |> Map.put(:group_by, "id") |> derive_group_by else options @@ -175,7 +206,7 @@ defmodule SqlDust do conditions = List.wrap(conditions) [head | tail] = conditions - if is_bitstring(head) && (length(Regex.scan(~r/\?/, head)) == length(tail)) do + if is_bitstring(head) && length(Regex.scan(~r/\?/, head)) == length(tail) do [conditions] else Enum.map(conditions, &List.wrap/1) @@ -183,32 +214,40 @@ defmodule SqlDust do end defp parse_conditions([], options, _), do: options + defp parse_conditions(conditions, options, key) when key in [:where, :having] do {conditions, options} = conditions - |> Enum.reduce({[], options}, fn([sql | values], {conditions, options}) -> + |> Enum.reduce({[], options}, fn [sql | values], {conditions, options} -> {sql, options} = prepend_path_aliases("(" <> sql <> ")", options) {[[sql | values] | conditions], options} end) + conditions = Enum.reverse(conditions) parse_conditions(conditions, options, key, true) end defp parse_conditions(conditions, options, key, _ \\ true) do - {conditions, options} = Enum.reduce(conditions, {[], options}, fn([sql | values], {conditions, options}) -> - {sql, variables} = values - |> Enum.reduce({sql, options.variables}, fn(value, {sql, variables}) -> - key = "__" <> to_string(Map.size(variables) + 1) <> "__" - variables = Map.put(variables, key, value) - sql = String.replace(sql, "?", "<<" <> key <> ">>", global: false) - {sql, variables} - end) - - options = Map.put(options, :variables, variables) - {[sql | conditions], options} - end) + {conditions, options} = + Enum.reduce(conditions, {[], options}, fn [sql | values], {conditions, options} -> + {sql, variables} = + values + |> Enum.reduce({sql, options.variables}, fn value, {sql, variables} -> + key = "__" <> to_string(Kernel.map_size(variables) + 1) <> "__" + variables = Map.put(variables, key, value) + sql = String.replace(sql, "?", "<<" <> key <> ">>", global: false) + {sql, variables} + end) + + options = Map.put(options, :variables, variables) + {[sql | conditions], options} + end) + conditions = Enum.reverse(conditions) - prefix = if key in [:where, :having], do: (Atom.to_string(key) |> String.upcase) <> " ", else: "" + + prefix = + if key in [:where, :having], do: (Atom.to_string(key) |> String.upcase()) <> " ", else: "" + Map.put(options, key, prefix <> (conditions |> Enum.join(" AND "))) end @@ -226,17 +265,15 @@ defmodule SqlDust do interpolated_key = " <<_options_." <> Atom.to_string(key) <> ">>" options - |> Map.put(key, (Atom.to_string(key) |> String.upcase) <> interpolated_key) - |> Map.put(:variables, variables) + |> Map.put(key, (Atom.to_string(key) |> String.upcase()) <> interpolated_key) + |> Map.put(:variables, variables) end defp compose_sql(options) do [ options.select, options.from, - options.joins |> Enum.map( - fn(x) -> elem(x, 1) end - ), + options.joins |> Enum.map(fn x -> elem(x, 1) end), options[:where], options[:group_by], options[:having], @@ -245,9 +282,9 @@ defmodule SqlDust do options[:offset], "" ] - |> List.flatten - |> Enum.reject(&is_nil/1) - |> Enum.join("\n") - |> interpolate_variables(options.variables, options.initial_variables) + |> List.flatten() + |> Enum.reject(&is_nil/1) + |> Enum.join("\n") + |> interpolate_variables(options.variables, options.initial_variables) end end diff --git a/lib/sql_dust/utils/compose_utils.ex b/lib/sql_dust/utils/compose_utils.ex index 92d5967..058d354 100644 --- a/lib/sql_dust/utils/compose_utils.ex +++ b/lib/sql_dust/utils/compose_utils.ex @@ -3,50 +3,133 @@ defmodule SqlDust.ComposeUtils do defmacro __using__(_) do quote do - def select(args) do select(options(), args) end - def select(options, args) do append(options, :select, args) end + def select(args) do + select(options(), args) + end - def from(resource) do from(options(), resource) end - def from(options, resource) do __from__(options, resource) end - defp __from__(options, resource) do put(options, :from, resource) end + def select(options, args) do + append(options, :select, args) + end - def join_on(statements) do join_on(options(), statements) end - def join_on(options, statements) do append(options, :join_on, statements, false) end + def from(resource) do + from(options(), resource) + end - def where(statements) do where(options(), statements) end - def where(options, statements) do append(options, :where, statements, false) end + def from(options, resource) do + __from__(options, resource) + end - def group_by(args) do group_by(options(), args) end - def group_by(options, args) do append(options, :group_by, args) end + defp __from__(options, resource) do + put(options, :from, resource) + end + + def join_on(statements) do + join_on(options(), statements) + end - def order_by(args) do order_by(options(), args) end - def order_by(options, args) do append(options, :order_by, args) end + def join_on(options, statements) do + append(options, :join_on, statements, false) + end - def limit do limit("?") end - def limit(%{} = options) do limit(options, "?") end - def limit(arg) do limit(options(), arg) end - def limit(options, arg) do put(options, :limit, arg) end + def where(statements) do + where(options(), statements) + end - def offset do offset("?") end - def offset(%{} = options) do offset(options, "?") end - def offset(arg) do offset(options(), arg) end - def offset(options, arg) do put(options, :offset, arg) end + def where(options, statements) do + append(options, :where, statements, false) + end - def unique do unique(true) end - def unique(%{} = options) do unique(options, true) end - def unique(bool) do unique(options(), bool) end - def unique(options, bool) do put(options, :unique, bool) end + def group_by(args) do + group_by(options(), args) + end - def variables(map) do variables(options(), map) end - def variables(options, map) do merge(options, :variables, map) end + def group_by(options, args) do + append(options, :group_by, args) + end - def adapter(name) do adapter(options(), name) end - def adapter(options, name) do put(options, :adapter, name) end + def order_by(args) do + order_by(options(), args) + end - def schema(map) do schema(options(), map) end - def schema(options, map) do merge(options, :schema, map) end + def order_by(options, args) do + append(options, :order_by, args) + end - defp parse(options) do __parse__(options) end + def limit do + limit("?") + end + + def limit(%{} = options) do + limit(options, "?") + end + + def limit(arg) do + limit(options(), arg) + end + + def limit(options, arg) do + put(options, :limit, arg) + end + + def offset do + offset("?") + end + + def offset(%{} = options) do + offset(options, "?") + end + + def offset(arg) do + offset(options(), arg) + end + + def offset(options, arg) do + put(options, :offset, arg) + end + + def unique do + unique(true) + end + + def unique(%{} = options) do + unique(options, true) + end + + def unique(bool) do + unique(options(), bool) + end + + def unique(options, bool) do + put(options, :unique, bool) + end + + def variables(map) do + variables(options(), map) + end + + def variables(options, map) do + merge(options, :variables, map) + end + + def adapter(name) do + adapter(options(), name) + end + + def adapter(options, name) do + put(options, :adapter, name) + end + + def schema(map) do + schema(options(), map) + end + + def schema(options, map) do + merge(options, :schema, map) + end + + defp parse(options) do + __parse__(options) + end defp __parse__(options) do if is_bitstring(options) do @@ -59,17 +142,30 @@ defmodule SqlDust.ComposeUtils do def to_sql(options) do options = parse(options) - if (is_nil(options.from)) do; + if is_nil(options.from) do raise "missing :from option in query dust" end from = options.from schema = options.schema - options = Map.take(options, [:select, :join_on, :where, :group_by, :order_by, :limit, :offset, :unique, :variables, :adapter]) - |> Enum.reduce(%{from: options.from}, fn - ({_key, nil}, map) -> map - ({key, value}, map) -> Map.put(map, key, value) - end) + + options = + Map.take(options, [ + :select, + :join_on, + :where, + :group_by, + :order_by, + :limit, + :offset, + :unique, + :variables, + :adapter + ]) + |> Enum.reduce(%{from: options.from}, fn + {_key, nil}, map -> map + {key, value}, map -> Map.put(map, key, value) + end) SqlDust.from(from, options, schema || %{}) end @@ -93,18 +189,20 @@ defmodule SqlDust.ComposeUtils do list |> List.insert_at(-1, value) end - Map.put options, key, list + Map.put(options, key, list) end defp merge(options, key, value) do options = parse(options) - value = (Map.get(options, key) || %{}) + + value = + (Map.get(options, key) || %{}) |> MapUtils.deep_merge(value) - Map.put options, key, value + Map.put(options, key, value) end - defoverridable [from: 2, parse: 1] + defoverridable from: 2, parse: 1 end end end diff --git a/lib/sql_dust/utils/join_utils.ex b/lib/sql_dust/utils/join_utils.ex index e9d93a1..eba0146 100644 --- a/lib/sql_dust/utils/join_utils.ex +++ b/lib/sql_dust/utils/join_utils.ex @@ -8,21 +8,26 @@ defmodule SqlDust.JoinUtils do {path, association} = dissect_path(path, options) derive_schema(path, association, options) - |> derive_table_joins(path, association, options) - |> compose_join(options) + |> derive_table_joins(path, association, options) + |> compose_join(options) end defp derive_table_joins(schema1, path, association, options) do - schema1 = schema1 + schema1 = + schema1 |> Map.put(:path, path) - schema2 = MapUtils.get(schema1, association) + schema2 = + MapUtils.get(schema1, association) |> MapUtils.get(:resource, Inflex.pluralize(association)) |> resource_schema(options) - |> Map.put(:path, case path do - "" -> association - _ -> "#{path}.#{association}" - end) + |> Map.put( + :path, + case path do + "" -> association + _ -> "#{path}.#{association}" + end + ) association = MapUtils.get(schema1, association) @@ -34,8 +39,10 @@ defmodule SqlDust.JoinUtils do cardinality: cardinality, table: association[:table_name] || schema2.table_name, path: schema2.path, - left_key: "#{schema2.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", - right_key: "#{schema1.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", + left_key: + "#{schema2.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", + right_key: + "#{schema1.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", join_on: derive_join_on(schema2.path, association) } end @@ -45,8 +52,10 @@ defmodule SqlDust.JoinUtils do cardinality: cardinality, table: association[:table_name] || schema2.table_name, path: schema2.path, - left_key: "#{schema2.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", - right_key: "#{schema1.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", + left_key: + "#{schema2.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", + right_key: + "#{schema1.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", join_on: derive_join_on(schema2.path, association) } end @@ -56,26 +65,39 @@ defmodule SqlDust.JoinUtils do cardinality: cardinality, table: association[:table_name] || schema2.table_name, path: schema2.path, - left_key: "#{schema2.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", - right_key: "#{schema1.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", + left_key: + "#{schema2.path |> quote_alias(options)}.#{association.foreign_key |> quote_alias(options)}", + right_key: + "#{schema1.path |> quote_alias(options)}.#{association.primary_key |> quote_alias(options)}", join_on: derive_join_on(schema2.path, association) } end - defp derive_schema_joins(:has_and_belongs_to_many = cardinality, schema1, schema2, association, options) do + defp derive_schema_joins( + :has_and_belongs_to_many = cardinality, + schema1, + schema2, + association, + options + ) do [ %{ cardinality: cardinality, table: association.bridge_table, path: "#{schema2.path}_bridge_table", - left_key: "#{quote_alias(schema2.path <> "_bridge_table", options)}.#{quote_alias(association.foreign_key, options)}", - right_key: "#{quote_alias(schema1.path, options)}.#{quote_alias(association.primary_key, options)}" - }, %{ + left_key: + "#{quote_alias(schema2.path <> "_bridge_table", options)}.#{quote_alias(association.foreign_key, options)}", + right_key: + "#{quote_alias(schema1.path, options)}.#{quote_alias(association.primary_key, options)}" + }, + %{ cardinality: cardinality, table: schema2.table_name, path: schema2.path, - left_key: "#{quote_alias(schema2.path, options)}.#{quote_alias(association.association_primary_key, options)}", - right_key: "#{quote_alias(schema2.path <> "_bridge_table", options)}.#{quote_alias(association.association_foreign_key, options)}" + left_key: + "#{quote_alias(schema2.path, options)}.#{quote_alias(association.association_primary_key, options)}", + right_key: + "#{quote_alias(schema2.path <> "_bridge_table", options)}.#{quote_alias(association.association_foreign_key, options)}" } ] end @@ -84,15 +106,18 @@ defmodule SqlDust.JoinUtils do regex = ~r/(?:\.\*|[a-zA-Z]\w+(?:\.(?:\*|\w{2,}))*)/ association[:join_on] - |> List.wrap - |> Enum.map(fn(sql) -> - {excluded, _} = scan_excluded(sql) - sql = numerize_patterns(sql, excluded) - sql = Regex.replace(regex, sql, fn(match) -> + |> List.wrap() + |> Enum.map(fn sql -> + {excluded, _} = scan_excluded(sql) + sql = numerize_patterns(sql, excluded) + + sql = + Regex.replace(regex, sql, fn match -> path <> "." <> match end) - interpolate_patterns(sql, excluded) - end) + + interpolate_patterns(sql, excluded) + end) end defp compose_join(table_joins, options) when is_map(table_joins) do @@ -101,26 +126,37 @@ defmodule SqlDust.JoinUtils do defp compose_join(table_joins, options) do table_joins - |> Enum.map(fn(join) -> - {left_key, _} = prepend_path_alias(join.left_key, options) - {right_key, _} = prepend_path_alias(join.right_key, options) - - additional_conditions = join[:join_on] - |> List.wrap - |> Enum.concat(additional_join_conditions(join.path, options)) - |> Enum.map(fn(statement) -> - elem prepend_path_aliases(statement, options), 0 - end) - - conditions = [left_key <> " = " <> right_key] - |> Enum.concat(additional_conditions) - |> Enum.join(" AND ") - - {join.cardinality, ["LEFT JOIN", quote_alias(join.table, options), derive_quoted_path_alias(join.path, options), "ON", conditions] |> Enum.join(" ")} - end) + |> Enum.map(fn join -> + {left_key, _} = prepend_path_alias(join.left_key, options) + {right_key, _} = prepend_path_alias(join.right_key, options) + + additional_conditions = + join[:join_on] + |> List.wrap() + |> Enum.concat(additional_join_conditions(join.path, options)) + |> Enum.map(fn statement -> + elem(prepend_path_aliases(statement, options), 0) + end) + + conditions = + [left_key <> " = " <> right_key] + |> Enum.concat(additional_conditions) + |> Enum.join(" AND ") + + {join.cardinality, + [ + "LEFT JOIN", + quote_alias(join.table, options), + derive_quoted_path_alias(join.path, options), + "ON", + conditions + ] + |> Enum.join(" ")} + end) end - defp additional_join_conditions(path, %{join_on: join_on} = options) when is_bitstring(join_on) do + defp additional_join_conditions(path, %{join_on: join_on} = options) + when is_bitstring(join_on) do additional_join_conditions(path, %{options | join_on: [join_on]}) end @@ -128,14 +164,15 @@ defmodule SqlDust.JoinUtils do path_alias = derive_quoted_path_alias(path, options) join_on - |> Enum.reduce([], fn(statement, conditions) -> - {sql, _} = prepend_path_aliases(statement, options) - if String.contains?(sql, path_alias) do - conditions |> List.insert_at(-1, statement) - else - conditions - end - end) + |> Enum.reduce([], fn statement, conditions -> + {sql, _} = prepend_path_aliases(statement, options) + + if String.contains?(sql, path_alias) do + conditions |> List.insert_at(-1, statement) + else + conditions + end + end) end defp additional_join_conditions(_, _), do: [] diff --git a/lib/sql_dust/utils/map_utils.ex b/lib/sql_dust/utils/map_utils.ex index 51c2faa..9bf3dbb 100644 --- a/lib/sql_dust/utils/map_utils.ex +++ b/lib/sql_dust/utils/map_utils.ex @@ -1,28 +1,33 @@ defmodule SqlDust.MapUtils do def get(map, key, default \\ nil) + def get(map, key, default) when is_atom(key) do Map.get(map, key, Map.get(map, Atom.to_string(key), default)) end + def get(map, key, default) when is_bitstring(key) do Map.get(map, key, Map.get(map, String.to_atom(key), default)) end + def get(map, key, default), do: get(map, to_string(key), default) def deep_merge(map1, map2) do Map.keys(map1) - |> Enum.concat(Map.keys(map2)) - |> Enum.reduce(%{}, fn - (key, map) when is_atom(key) -> - val = proceed_key(key, map1, map2) - Map.put(map, key, val) - (key, map) -> - val = proceed_key(key, map1, map2) - Map.put(map, String.to_atom(key), val) - end) + |> Enum.concat(Map.keys(map2)) + |> Enum.reduce(%{}, fn + key, map when is_atom(key) -> + val = proceed_key(key, map1, map2) + Map.put(map, key, val) + + key, map -> + val = proceed_key(key, map1, map2) + Map.put(map, String.to_atom(key), val) + end) end defp proceed_key(key, map1, map2) do {val1, val2} = {get(map1, key), get(map2, key)} + cond do is_map(val1) -> deep_merge(val1, val2 || %{}) has_key?(map2, key) -> val2 @@ -33,6 +38,7 @@ defmodule SqlDust.MapUtils do defp has_key?(map, key) when is_atom(key) do Map.has_key?(map, key) || Map.has_key?(map, Atom.to_string(key)) end + defp has_key?(map, key) do Map.has_key?(map, key) || Map.has_key?(map, String.to_atom(key)) end diff --git a/lib/sql_dust/utils/path_utils.ex b/lib/sql_dust/utils/path_utils.ex index 00d4754..6357308 100644 --- a/lib/sql_dust/utils/path_utils.ex +++ b/lib/sql_dust/utils/path_utils.ex @@ -3,17 +3,23 @@ defmodule SqlDust.PathUtils do import SqlDust.ScanUtils def prepend_path_aliases([], options), do: {[], options} + def prepend_path_aliases([sql_line | sql_lines], options) do {prepended_sql_line, prepended_options} = prepend_path_aliases(sql_line, options) - {prepended_sql_lines, acc_prepended_options} = prepend_path_aliases(sql_lines, prepended_options) + + {prepended_sql_lines, acc_prepended_options} = + prepend_path_aliases(sql_lines, prepended_options) {[prepended_sql_line | prepended_sql_lines], acc_prepended_options} end + def prepend_path_aliases(sql, options) when sql == "*", do: {sql, options} + def prepend_path_aliases(sql, options) when is_binary(sql) do {sql, aliases, excluded} = scan_and_format_aliases(sql, options) {sql, dust_paths} = scan_and_replace_dust_paths(sql) options = Map.put(options, :aliases, aliases) + {sql, options} = sql |> numerize_patterns(excluded) @@ -26,22 +32,26 @@ defmodule SqlDust.PathUtils do defp scan_and_replace_dust_paths(sql) do path_regex = ~r/\[([\w\d\._]+)\]/i - dust_paths = Regex.scan(path_regex, sql) |> Enum.map( &List.first(&1) ) - {sql, _} = Enum.reduce(dust_paths, {sql, 0}, fn(path, {sql, index}) -> - index_str = "[#{index}]" - {String.replace(sql, path, index_str), index + 1} - end) + dust_paths = Regex.scan(path_regex, sql) |> Enum.map(&List.first(&1)) + + {sql, _} = + Enum.reduce(dust_paths, {sql, 0}, fn path, {sql, index} -> + index_str = "[#{index}]" + {String.replace(sql, path, index_str), index + 1} + end) {sql, dust_paths} end + defp restore_dust_paths(sql, []), do: sql + defp restore_dust_paths(sql, dust_paths) do - (0..length(dust_paths) - 1) - |> Enum.reduce(sql, fn(index, sql) -> - search_string = "[#{index}]" - replace_string = Enum.at(dust_paths, index) |> String.slice(1..-2) - String.replace(sql, search_string, replace_string) - end) + 0..(length(dust_paths) - 1) + |> Enum.reduce(sql, fn index, sql -> + search_string = "[#{index}]" + replace_string = Enum.at(dust_paths, index) |> String.slice(1..-2) + String.replace(sql, search_string, replace_string) + end) end defp scan_and_format_aliases(sql, options) when is_binary(sql) do @@ -51,7 +61,7 @@ defmodule SqlDust.PathUtils do aliases |> Enum.map(&fix_sql_alias/1) |> Enum.concat(options.aliases) - |> Enum.uniq + |> Enum.uniq() excluded = format_excluded_aliases(excluded, aliases, options) @@ -60,20 +70,20 @@ defmodule SqlDust.PathUtils do defp format_excluded_aliases(excluded, aliases, options) do excluded - |> Enum.map(fn(excluded) -> - if match_excluded_alias(excluded) do - {_, compiled} = Regex.compile(excluded) - - [compiled, " AS " <> quote_alias(fix_sql_alias(excluded), options)] - else - excluded - end - end) + |> Enum.map(fn excluded -> + if match_excluded_alias(excluded) do + {_, compiled} = Regex.compile(excluded) + + [compiled, " AS " <> quote_alias(fix_sql_alias(excluded), options)] + else + excluded + end + end) |> Enum.concat( - Enum.map(aliases, fn(sql_alias) -> - [~r/([^\.\w])#{sql_alias}([^\.\w])/, quote_alias(sql_alias, options)] - end) - ) + Enum.map(aliases, fn sql_alias -> + [~r/([^\.\w])#{sql_alias}([^\.\w])/, quote_alias(sql_alias, options)] + end) + ) end defp fix_sql_alias(" AS " <> sql_alias), do: sql_alias @@ -88,40 +98,45 @@ defmodule SqlDust.PathUtils do def sanitize_sql(sql) do {excluded, _} = scan_excluded(sql) - Enum.reduce(excluded, sql, fn(pattern, sql) -> + + Enum.reduce(excluded, sql, fn pattern, sql -> String.replace(sql, pattern, "") end) end def scan_excluded(sql) do - excluded = [] + excluded = + [] |> Enum.concat(scan_strings(sql)) |> Enum.concat(scan_variables(sql)) |> Enum.concat(scan_functions(sql)) - |> Enum.concat(aliases = (sql |> scan_aliases() |> List.flatten |> Enum.uniq)) + |> Enum.concat(aliases = sql |> scan_aliases() |> List.flatten() |> Enum.uniq()) |> Enum.concat(scan_reserved_words(sql)) - |> List.flatten - |> Enum.uniq + |> List.flatten() + |> Enum.uniq() {excluded, aliases} end defp scan_and_prepend_path_aliases(sql, options) do ~r/(?:\.\*|\w*[a-zA-Z]+\w*(?:\.(?:\*|\w{2,}))*)/ - |> Regex.split(sql, [include_captures: true]) + |> Regex.split(sql, include_captures: true) |> analyze_aliases([], options, false) end defp analyze_aliases([h], out, options, false) do {[h | out] |> Enum.reverse() |> Enum.join(""), options} end + defp analyze_aliases([path], out, options, true) do {path_alias, options} = prepend_path_alias(path, options, true) analyze_aliases([path_alias], out, options, false) end + defp analyze_aliases([h | t], out, options, false) do analyze_aliases(t, [h | out], options, true) end + defp analyze_aliases([path | rest], out, options, true) do {path_alias, options} = prepend_path_alias(path, options, true) analyze_aliases(rest, [path_alias | out], options, false) @@ -149,9 +164,11 @@ defmodule SqlDust.PathUtils do end defp do_dissect_path(path, options) do - quotation_symbol = quotation_mark(options) - split_on_dot_outside_quotation_mark = ~r/\.(?=(?:[^#{quotation_symbol}]*#{quotation_symbol}[^#{quotation_symbol}]*#{quotation_symbol})*[^#{quotation_symbol}]*$)/ + + split_on_dot_outside_quotation_mark = + ~r/\.(?=(?:[^#{quotation_symbol}]*#{quotation_symbol}[^#{quotation_symbol}]*#{quotation_symbol})*[^#{quotation_symbol}]*$)/ + segments = String.split(path, split_on_dot_outside_quotation_mark) case Enum.split(segments, -1) do @@ -167,9 +184,9 @@ defmodule SqlDust.PathUtils do defp cascaded_paths(path) do path |> Enum.reduce([], fn - (segment, paths) when segment in [nil, ""] -> paths - (segment, []) -> [segment] - (segment, [h | _] = paths) -> [h <> "." <> segment | paths] + segment, paths when segment in [nil, ""] -> paths + segment, [] -> [segment] + segment, [h | _] = paths -> [h <> "." <> segment | paths] end) |> Enum.reverse() end @@ -203,6 +220,7 @@ defmodule SqlDust.PathUtils do def quote_alias(sql, options) do quotation_symbol = quotation_mark(options) + if Regex.match?(~r/\A#{quotation_symbol}.*#{quotation_symbol}\z/, sql) do sql else diff --git a/lib/sql_dust/utils/scan_utils.ex b/lib/sql_dust/utils/scan_utils.ex index ba21522..e557f94 100644 --- a/lib/sql_dust/utils/scan_utils.ex +++ b/lib/sql_dust/utils/scan_utils.ex @@ -5,9 +5,9 @@ defmodule SqlDust.ScanUtils do def split_arguments(sql) when is_list(sql) do sql - |> List.flatten - |> Enum.join(", ") - |> split_arguments + |> List.flatten() + |> Enum.join(", ") + |> split_arguments end def split_arguments(sql) do @@ -21,7 +21,7 @@ defmodule SqlDust.ScanUtils do {list, _} = sql |> String.split(~r/\s*,\s*/) - |> Enum.reduce({[], excluded}, fn(sql, {list, excluded}) -> + |> Enum.reduce({[], excluded}, fn sql, {list, excluded} -> sql = interpolate_parenthesized(sql, excluded) {[sql | list], excluded} end) @@ -35,19 +35,21 @@ defmodule SqlDust.ScanUtils do if length(parenthesized) == 0 do {sql, patterns} else - patterns = patterns + patterns = + patterns |> Enum.concat(parenthesized) - |> List.flatten - |> Enum.uniq + |> List.flatten() + |> Enum.uniq() + numerize_patterns(sql, patterns) - |> numerize_parenthesized(patterns) + |> numerize_parenthesized(patterns) end end defp interpolate_parenthesized(sql, patterns) do if String.match?(sql, ~r/\{\d+\}/) do interpolate_patterns(sql, patterns) - |> interpolate_parenthesized(patterns) + |> interpolate_parenthesized(patterns) else sql end @@ -55,11 +57,11 @@ defmodule SqlDust.ScanUtils do def scan_strings(sql) do Regex.scan(~r/(["'])(?:(?=(\\?))\2.)*?\1/, sql) - |> Enum.reduce([], fn - ([""|_], strings) -> strings - ([match|_], strings) -> [match | strings] - end) - |> Enum.reverse() + |> Enum.reduce([], fn + ["" | _], strings -> strings + [match | _], strings -> [match | strings] + end) + |> Enum.reverse() end def scan_variables(sql) do @@ -83,29 +85,37 @@ defmodule SqlDust.ScanUtils do end def scan_reserved_words(sql) do - Regex.scan(~r/\b(distinct|key|and|or|is|like|rlike|regexp|in|between|not|null|sounds|soundex|asc|desc|true|false)\b/i, sql) + Regex.scan( + ~r/\b(distinct|key|and|or|is|like|rlike|regexp|in|between|not|null|sounds|soundex|asc|desc|true|false)\b/i, + sql + ) end def numerize_patterns(sql, patterns) do Enum.reduce(patterns, {sql, 0}, fn - ([regex | _] = pattern, {sql, index}) when is_list(pattern) -> + [regex | _] = pattern, {sql, index} when is_list(pattern) -> index = index + 1 index_str = "{" <> to_string(index) <> "}" - {Regex.replace(regex, sql, fn(_, prefix, postfix) -> - prefix <> index_str <> postfix - end), index} - (pattern, {sql, index}) -> + + {Regex.replace(regex, sql, fn _, prefix, postfix -> + prefix <> index_str <> postfix + end), index} + + pattern, {sql, index} -> index = index + 1 index_str = "{" <> to_string(index) <> "}" + sql = if Regex.match?(~r/^\w+$/, pattern) do {_, regex} = Regex.compile("(^|\\b)" <> pattern <> "(\\b|$)") - Regex.replace(regex, sql, fn(_, prefix, postfix) -> + + Regex.replace(regex, sql, fn _, prefix, postfix -> prefix <> index_str <> postfix end) else String.replace(sql, pattern, index_str) end + {sql, index} end) |> elem(0) @@ -114,9 +124,9 @@ defmodule SqlDust.ScanUtils do def interpolate_patterns(sql, patterns) do patterns |> Enum.reduce({sql, 0}, fn - ([_, pattern], {sql, index}) -> interpolate_pattern(pattern, sql, index) - ([_ | [pattern | _]], {sql, index}) -> interpolate_pattern(pattern, sql, index) - (pattern, {sql, index}) -> interpolate_pattern(pattern, sql, index) + [_, pattern], {sql, index} -> interpolate_pattern(pattern, sql, index) + [_ | [pattern | _]], {sql, index} -> interpolate_pattern(pattern, sql, index) + pattern, {sql, index} -> interpolate_pattern(pattern, sql, index) end) |> elem(0) end @@ -130,30 +140,29 @@ defmodule SqlDust.ScanUtils do excluded = scan_strings(sql) sql = numerize_patterns(sql, excluded) - {sql, values, keys} = @variable_regex |> Regex.scan(sql) - |> Enum.reduce({sql, [], []}, fn([match, key], {sql, values, keys}) -> - value = String.split(key, ".") |> Enum.reduce(variables, fn(name, variables) -> - MapUtils.get(variables, name) - end) + |> Enum.reduce({sql, [], []}, fn [match, key], {sql, values, keys} -> + value = + String.split(key, ".") + |> Enum.reduce(variables, fn name, variables -> + 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))) - ) - ) - - sql = String.replace sql, match, "?", global: false + 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)))) + + sql = String.replace(sql, match, "?", global: false) values = [value | values] key = if anonymous_key, do: nil, else: key keys = [key | keys] @@ -164,10 +173,12 @@ 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 = + 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) if include_keys do {sql, values, keys} diff --git a/lib/sql_dust/utils/schema_utils.ex b/lib/sql_dust/utils/schema_utils.ex index c8efef0..db8bfab 100644 --- a/lib/sql_dust/utils/schema_utils.ex +++ b/lib/sql_dust/utils/schema_utils.ex @@ -3,24 +3,24 @@ defmodule SqlDust.SchemaUtils do def derive_schema(path, association, options) when is_bitstring(path) do String.split(path, ".") - |> Enum.reduce([], fn - ("", segments) -> segments - (segment, segments) -> [segment | segments] - end) - |> Enum.reverse() - |> derive_schema(association, options) + |> Enum.reduce([], fn + "", segments -> segments + segment, segments -> [segment | segments] + end) + |> Enum.reverse() + |> derive_schema(association, options) end def derive_schema(path, association, options) do path - |> Enum.reduce(options.resource.name, fn(path_segment, resource) -> - options - |> MapUtils.get(:schema, %{}) - |> MapUtils.get(resource, %{}) - |> MapUtils.get(path_segment, %{}) - |> MapUtils.get(:resource, Inflex.pluralize(path_segment)) - end) - |> resource_schema(association, options) + |> Enum.reduce(options.resource.name, fn path_segment, resource -> + options + |> MapUtils.get(:schema, %{}) + |> MapUtils.get(resource, %{}) + |> MapUtils.get(path_segment, %{}) + |> MapUtils.get(:resource, Inflex.pluralize(path_segment)) + end) + |> resource_schema(association, options) end def resource_schema(resource, options) do @@ -32,8 +32,8 @@ defmodule SqlDust.SchemaUtils do cardinality = MapUtils.get(MapUtils.get(schema, association, %{}), :cardinality) defacto_schema(resource) - |> Map.merge(defacto_association(resource, association, cardinality)) - |> MapUtils.deep_merge(schema) + |> Map.merge(defacto_association(resource, association, cardinality)) + |> MapUtils.deep_merge(schema) end defp defacto_schema(resource) do @@ -48,31 +48,42 @@ defmodule SqlDust.SchemaUtils do defp defacto_association(resource, association, cardinality) do cardinality = cardinality || derive_cardinality(association) - map = case cardinality do - :belongs_to -> %{ - primary_key: "id", - foreign_key: "#{association}_id" - } - :has_one -> %{ - primary_key: "id", - foreign_key: "#{Inflex.singularize(resource)}_id" - } - :has_many -> %{ - primary_key: "id", - foreign_key: "#{Inflex.singularize(resource)}_id" - } - :has_and_belongs_to_many -> %{ - bridge_table: ([Inflex.pluralize(resource), Inflex.pluralize(association)] |> Enum.sort |> Enum.join("_")), - primary_key: "id", - foreign_key: "#{Inflex.singularize(resource)}_id", - association_primary_key: "id", - association_foreign_key: "#{Inflex.singularize(association)}_id" - } - end + map = + case cardinality do + :belongs_to -> + %{ + primary_key: "id", + foreign_key: "#{association}_id" + } + + :has_one -> + %{ + primary_key: "id", + foreign_key: "#{Inflex.singularize(resource)}_id" + } + + :has_many -> + %{ + primary_key: "id", + foreign_key: "#{Inflex.singularize(resource)}_id" + } + + :has_and_belongs_to_many -> + %{ + bridge_table: + [Inflex.pluralize(resource), Inflex.pluralize(association)] + |> Enum.sort() + |> Enum.join("_"), + primary_key: "id", + foreign_key: "#{Inflex.singularize(resource)}_id", + association_primary_key: "id", + association_foreign_key: "#{Inflex.singularize(association)}_id" + } + end |> Map.put(:cardinality, cardinality) %{} - |> Map.put(association, map) + |> Map.put(association, map) end defp derive_cardinality(association) do diff --git a/mix.exs b/mix.exs index 53a4e1e..c706be6 100644 --- a/mix.exs +++ b/mix.exs @@ -2,18 +2,20 @@ defmodule SqlDust.Mixfile do use Mix.Project def project do - [app: :sql_dust, - version: "0.4.0", - elixir: "~> 1.4", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - description: description(), - package: package(), - deps: deps()] + [ + app: :sql_dust, + version: "0.4.0", + elixir: "~> 1.4", + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + description: description(), + package: package(), + deps: deps() + ] end def application do - [applications: [:logger]] + [applications: [:logger, :inflex]] end defp deps do @@ -23,7 +25,7 @@ defmodule SqlDust.Mixfile do {:exprof, "~> 0.2", only: :dev}, {:benchfella, "~> 0.3", only: :dev}, {:ex_doc, "~> 0.25", only: :dev}, - {:credo, "~> 1.5", only: [:dev, :test]}, + {:credo, "~> 1.5", only: [:dev, :test]} ] end diff --git a/test/ecto/sql_dust_test.exs b/test/ecto/sql_dust_test.exs index 0818a5a..6401e17 100644 --- a/test/ecto/sql_dust_test.exs +++ b/test/ecto/sql_dust_test.exs @@ -1,23 +1,26 @@ defmodule Test do defmodule Weather do use Ecto.Schema + schema "weather" do - belongs_to :city, Test.City + belongs_to(:city, Test.City) end end defmodule City do use Ecto.Schema + schema "cities" do - has_many :local_weather, Test.Weather - belongs_to :country, Test.Country + has_many(:local_weather, Test.Weather) + belongs_to(:country, Test.Country) end end defmodule Country do use Ecto.Schema + schema "countries" do - has_many :cities, Test.City + has_many(:cities, Test.City) # has_many :weather, through: [:cities, :local_weather] ??? end end @@ -29,63 +32,71 @@ defmodule Ecto.SqlDustTest do import Ecto.SqlDust test "generates simple SQL based on Ecto model schemas" do - sql = Test.Weather + sql = + Test.Weather |> to_sql - assert sql == {""" - SELECT "w".* - FROM "weather" "w" - """, []} + assert sql == + {""" + SELECT "w".* + FROM "weather" "w" + """, []} end test "generates simple SQL based on Ecto model schemas (using from)" do - sql = from(Test.Weather) + sql = + from(Test.Weather) |> to_sql - assert sql == {""" - SELECT "w".* - FROM "weather" "w" - """, []} + assert sql == + {""" + SELECT "w".* + FROM "weather" "w" + """, []} end test "generates SQL based on Ecto model schemas" do - sql = Test.City + sql = + Test.City |> select("id, name, country.name, local_weather.temp_lo, local_weather.temp_hi") |> where("local_weather.wdate = '2015-09-12'") |> to_sql - assert sql == {""" - SELECT - "c"."id", - "c"."name", - "country"."name", - "local_weather"."temp_lo", - "local_weather"."temp_hi" - FROM "cities" "c" - LEFT JOIN "countries" "country" ON "country"."id" = "c"."country_id" - LEFT JOIN "weather" "local_weather" ON "local_weather"."city_id" = "c"."id" - WHERE ("local_weather"."wdate" = '2015-09-12') - """, []} + assert sql == + {""" + SELECT + "c"."id", + "c"."name", + "country"."name", + "local_weather"."temp_lo", + "local_weather"."temp_hi" + FROM "cities" "c" + LEFT JOIN "countries" "country" ON "country"."id" = "c"."country_id" + LEFT JOIN "weather" "local_weather" ON "local_weather"."city_id" = "c"."id" + WHERE ("local_weather"."wdate" = '2015-09-12') + """, []} end test "support generating SQL based on Ecto model schemas for MySQL" do - sql = Test.City + sql = + Test.City |> select("id, name, country.name, local_weather.temp_lo, local_weather.temp_hi") |> where(["local_weather.wdate = ?", "2015-09-12"]) |> adapter(:mysql) |> to_sql - assert sql == {""" - SELECT - `c`.`id`, - `c`.`name`, - `country`.`name`, - `local_weather`.`temp_lo`, - `local_weather`.`temp_hi` - FROM `cities` `c` - LEFT JOIN `countries` `country` ON `country`.`id` = `c`.`country_id` - LEFT JOIN `weather` `local_weather` ON `local_weather`.`city_id` = `c`.`id` - WHERE (`local_weather`.`wdate` = ?) - """, ["2015-09-12"]} + assert sql == + {""" + SELECT + `c`.`id`, + `c`.`name`, + `country`.`name`, + `local_weather`.`temp_lo`, + `local_weather`.`temp_hi` + FROM `cities` `c` + LEFT JOIN `countries` `country` ON `country`.`id` = `c`.`country_id` + LEFT JOIN `weather` `local_weather` ON `local_weather`.`city_id` = `c`.`id` + WHERE (`local_weather`.`wdate` = ?) + """, ["2015-09-12"]} end end diff --git a/test/sql_dust/map_utils_test.exs b/test/sql_dust/map_utils_test.exs index 24ec556..aa7aa9f 100644 --- a/test/sql_dust/map_utils_test.exs +++ b/test/sql_dust/map_utils_test.exs @@ -91,10 +91,10 @@ defmodule SqlDust.MapUtilsTest do end test "it merges string keys in nested maps" do - map1 = %{a: "3", b: %{"c" => %{d: %{ "e" => 9001 }}}} - map2 = %{a: "3", b: %{c: %{"d" => %{ "e" => 9001 }}}} + map1 = %{a: "3", b: %{"c" => %{d: %{"e" => 9001}}}} + map2 = %{a: "3", b: %{c: %{"d" => %{"e" => 9001}}}} map3 = MapUtils.deep_merge(map1, map2) - assert map3 == %{a: "3", b: %{c: %{d: %{ e: 9001 }}}} + assert map3 == %{a: "3", b: %{c: %{d: %{e: 9001}}}} end end diff --git a/test/sql_dust/query_test.exs b/test/sql_dust/query_test.exs index 20b7132..f9a55d7 100644 --- a/test/sql_dust/query_test.exs +++ b/test/sql_dust/query_test.exs @@ -7,442 +7,465 @@ defmodule SqlDust.QueryTest do query_dust = select("id") assert query_dust == %SqlDust{ - select: ["id"] - } + select: ["id"] + } end test "select returns SqlDust containing :select option (passing a list)" do query_dust = select(["id"]) assert query_dust == %SqlDust{ - select: ["id"] - } + select: ["id"] + } end test "select appends an argument to existing :select option (passing a string)" do - query_dust = select("id") + query_dust = + select("id") |> select("name") assert query_dust == %SqlDust{ - select: ["id", "name"] - } + select: ["id", "name"] + } end test "select appends an argument to existing :select option (passing a list)" do - query_dust = select("id") + query_dust = + select("id") |> select(~w(name)) assert query_dust == %SqlDust{ - select: ["id", "name"] - } + select: ["id", "name"] + } end test "passing a string to imply the :from option" do - query_dust = "users" + query_dust = + "users" |> select("id") assert query_dust == %SqlDust{ - select: ["id"], - from: "users" - } + select: ["id"], + from: "users" + } end test "from returns SqlDust containing :from option" do query_dust = from("users") assert query_dust == %SqlDust{ - from: "users" - } + from: "users" + } end test "from sets :from option of passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> from("users") assert query_dust == %SqlDust{ - select: ["id"], - from: "users" - } + select: ["id"], + from: "users" + } end test "where returns SqlDust containing :where option (passing a string)" do query_dust = where("company.name LIKE '%Engel%'") assert query_dust == %SqlDust{ - where: ["company.name LIKE '%Engel%'"] - } + where: ["company.name LIKE '%Engel%'"] + } end test "where returns SqlDust containing :where option (passing a list)" do query_dust = where(["company.name LIKE '%Engel%'"]) assert query_dust == %SqlDust{ - where: [["company.name LIKE '%Engel%'"]] - } + where: [["company.name LIKE '%Engel%'"]] + } end test "where appends an argument to existing :where option (passing a string)" do - query_dust = where("company.name LIKE '%Engel%'") + query_dust = + where("company.name LIKE '%Engel%'") |> where("category_id = 1") assert query_dust == %SqlDust{ - where: ["company.name LIKE '%Engel%'", "category_id = 1"] - } + where: ["company.name LIKE '%Engel%'", "category_id = 1"] + } end test "where appends an argument to existing :where option (passing a list)" do - query_dust = where(["company.name LIKE ?", "%Engel%"]) + query_dust = + where(["company.name LIKE ?", "%Engel%"]) |> where(["category_id = ?", 1]) assert query_dust == %SqlDust{ - where: [ - ["company.name LIKE ?", "%Engel%"], - ["category_id = ?", 1] - ] - } + where: [ + ["company.name LIKE ?", "%Engel%"], + ["category_id = ?", 1] + ] + } end test "variables returns SqlDust containing :variables option" do query_dust = variables(%{id: 1982}) assert query_dust == %SqlDust{ - variables: %{id: 1982} - } + variables: %{id: 1982} + } end test "variables merges an argument to existing :variables option" do - query_dust = variables(%{id: 1982}) + query_dust = + variables(%{id: 1982}) |> variables(%{name: "Paul Engel"}) assert query_dust == %SqlDust{ - variables: %{id: 1982, name: "Paul Engel"} - } + variables: %{id: 1982, name: "Paul Engel"} + } end test "group_by returns SqlDust containing :group_by option (passing a string)" do query_dust = group_by("company_id") assert query_dust == %SqlDust{ - group_by: ["company_id"] - } + group_by: ["company_id"] + } end test "group_by returns SqlDust containing :group_by option (passing a list)" do query_dust = group_by(["company_id"]) assert query_dust == %SqlDust{ - group_by: ["company_id"] - } + group_by: ["company_id"] + } end test "group_by appends an argument to existing :group_by option (passing a string)" do - query_dust = group_by("company_id") + query_dust = + group_by("company_id") |> group_by("category_id") assert query_dust == %SqlDust{ - group_by: ["company_id", "category_id"] - } + group_by: ["company_id", "category_id"] + } end test "group_by appends an argument to existing :group_by option (passing a list)" do - query_dust = group_by("company_id") + query_dust = + group_by("company_id") |> group_by(~w(category_id)) assert query_dust == %SqlDust{ - group_by: ["company_id", "category_id"] - } + group_by: ["company_id", "category_id"] + } end test "order_by returns SqlDust containing :order_by option (passing a string)" do query_dust = order_by("company_id") assert query_dust == %SqlDust{ - order_by: ["company_id"] - } + order_by: ["company_id"] + } end test "order_by returns SqlDust containing :order_by option (passing a list)" do query_dust = order_by(["company_id"]) assert query_dust == %SqlDust{ - order_by: ["company_id"] - } + order_by: ["company_id"] + } end test "order_by appends an argument to existing :order_by option (passing a string)" do - query_dust = order_by("company_id") + query_dust = + order_by("company_id") |> order_by("category_id") assert query_dust == %SqlDust{ - order_by: ["company_id", "category_id"] - } + order_by: ["company_id", "category_id"] + } end test "order_by appends an argument to existing :order_by option (passing a list)" do - query_dust = order_by("company_id") + query_dust = + order_by("company_id") |> order_by(~w(category_id)) assert query_dust == %SqlDust{ - order_by: ["company_id", "category_id"] - } + order_by: ["company_id", "category_id"] + } end test "limit returns SqlDust containing :limit option" do query_dust = limit(10) assert query_dust == %SqlDust{ - limit: 10 - } + limit: 10 + } end test "limit sets :limit option of passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> limit(10) assert query_dust == %SqlDust{ - select: ["id"], - limit: 10 - } + select: ["id"], + limit: 10 + } end test "limit defaults to '?'" do - query_dust = select("id") + query_dust = + select("id") |> limit assert query_dust == %SqlDust{ - select: ["id"], - limit: "?" - } + select: ["id"], + limit: "?" + } end test "limit overwrites :limit option within passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> limit(10) |> limit(100) assert query_dust == %SqlDust{ - select: ["id"], - limit: 100 - } + select: ["id"], + limit: 100 + } end test "limit default overwrites :limit option within passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> limit(10) |> limit assert query_dust == %SqlDust{ - select: ["id"], - limit: "?" - } + select: ["id"], + limit: "?" + } end test "offset returns SqlDust containing :offset option" do query_dust = offset(10) assert query_dust == %SqlDust{ - offset: 10 - } + offset: 10 + } end test "offset sets :offset option of passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> offset(10) assert query_dust == %SqlDust{ - select: ["id"], - offset: 10 - } + select: ["id"], + offset: 10 + } end test "offset overwrites :offset option within passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> offset(10) |> offset(100) assert query_dust == %SqlDust{ - select: ["id"], - offset: 100 - } + select: ["id"], + offset: 100 + } end test "offset defaults to '?'" do - query_dust = select("id") + query_dust = + select("id") |> offset assert query_dust == %SqlDust{ - select: ["id"], - offset: "?" - } + select: ["id"], + offset: "?" + } end test "offset default overwrites :offset option within passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> offset(10) |> offset assert query_dust == %SqlDust{ - select: ["id"], - offset: "?" - } + select: ["id"], + offset: "?" + } end test "unique returns SqlDust containing :unique option (at default true)" do query_dust = unique() assert query_dust == %SqlDust{ - unique: true - } + unique: true + } end test "unique returns SqlDust containing :unique option" do query_dust = unique(false) assert query_dust == %SqlDust{ - unique: false - } + unique: false + } end test "unique sets :unique option of passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> unique(true) assert query_dust == %SqlDust{ - select: ["id"], - unique: true - } + select: ["id"], + unique: true + } end test "unique overwrites :unique option within passed SqlDust" do - query_dust = select("id") + query_dust = + select("id") |> unique(true) |> unique(false) assert query_dust == %SqlDust{ - select: ["id"], - unique: false - } + select: ["id"], + unique: false + } end test "schema returns SqlDust containing :schema option" do - query_dust = schema(%{ - users: %{ - skills: %{ - cardinality: :has_and_belongs_to_many - } - } - }) - - assert query_dust == %SqlDust{ - schema: %{ + query_dust = + schema(%{ users: %{ skills: %{ cardinality: :has_and_belongs_to_many } } - } - } + }) + + assert query_dust == %SqlDust{ + schema: %{ + users: %{ + skills: %{ + cardinality: :has_and_belongs_to_many + } + } + } + } end test "schema merges argument to existing :schema option" do - query_dust = schema( - %{ - users: %{ - skills: %{ - cardinality: :has_and_belongs_to_many - } - } - } - ) - |> schema( - %{ - users: %{ - skills: %{ - primary_key: "identifier" - } - }, - relations: %{ - table_name: "users" + query_dust = + schema(%{ + users: %{ + skills: %{ + cardinality: :has_and_belongs_to_many } } - ) - - assert query_dust == %SqlDust{ - schema: %{ + }) + |> schema(%{ users: %{ skills: %{ - cardinality: :has_and_belongs_to_many, primary_key: "identifier" } }, relations: %{ table_name: "users" } - } - } + }) + + assert query_dust == %SqlDust{ + schema: %{ + users: %{ + skills: %{ + cardinality: :has_and_belongs_to_many, + primary_key: "identifier" + } + }, + relations: %{ + table_name: "users" + } + } + } end test "throwing an error when generating SQL without having assigned the :from option" do assert_raise RuntimeError, "missing :from option in query dust", fn -> select("id") - |> to_sql + |> to_sql end end test "it generates the select for columns that start with a number" do - {sql, _} = select("1st_address as 1st_address") - |> select("second_address") - |> from("users") - |> to_sql + {sql, _} = + select("1st_address as 1st_address") + |> select("second_address") + |> from("users") + |> to_sql assert sql == """ - SELECT - `u`.`1st_address` AS `1st_address`, - `u`.`second_address` - FROM `users` `u` - """ + SELECT + `u`.`1st_address` AS `1st_address`, + `u`.`second_address` + FROM `users` `u` + """ end test "it generates the select for columns that ends with a number" do - {sql, _} = select("address1 as address1") - |> select("second_address") - |> from("users") - |> to_sql + {sql, _} = + select("address1 as address1") + |> select("second_address") + |> from("users") + |> to_sql assert sql == """ - SELECT - `u`.`address1` AS `address1`, - `u`.`second_address` - FROM `users` `u` - """ + SELECT + `u`.`address1` AS `address1`, + `u`.`second_address` + FROM `users` `u` + """ end test "escapes fields with same name as function" do - {sql, _} = select("[regexp] as regexp") - |> select("[id]") - |> select("[key.in.value]") - |> select("'a' REGEXP '^[a-d]'") - |> select("'Ewout!' REGEXP '.*' as test2") - |> from("users") - |> where("[like] = 'ewout'") - |> to_sql + {sql, _} = + select("[regexp] as regexp") + |> select("[id]") + |> select("[key.in.value]") + |> select("'a' REGEXP '^[a-d]'") + |> select("'Ewout!' REGEXP '.*' as test2") + |> from("users") + |> where("[like] = 'ewout'") + |> to_sql assert sql == """ - SELECT - `u`.`regexp` AS `regexp`, - `u`.`id`, - `key.in`.`value`, - 'a' REGEXP '^[a-d]', - 'Ewout!' REGEXP '.*' AS `test2` - FROM `users` `u` - LEFT JOIN `keys` `key` ON `key`.`id` = `u`.`key_id` - LEFT JOIN `ins` `key.in` ON `key.in`.`id` = `key`.`in_id` - WHERE (`u`.`like` = 'ewout') - """ + SELECT + `u`.`regexp` AS `regexp`, + `u`.`id`, + `key.in`.`value`, + 'a' REGEXP '^[a-d]', + 'Ewout!' REGEXP '.*' AS `test2` + FROM `users` `u` + LEFT JOIN `keys` `key` ON `key`.`id` = `u`.`key_id` + LEFT JOIN `ins` `key.in` ON `key.in`.`id` = `key`.`in_id` + WHERE (`u`.`like` = 'ewout') + """ end test "generating SQL using composed query dust" do - sql = select("id, name") + sql = + select("id, name") |> select("company.name") |> select("company.address.city") |> select("company.1st_address") @@ -452,30 +475,28 @@ defmodule SqlDust.QueryTest do |> join_on(["company.address.is_current = ?", 1]) |> order_by("name, company.name") |> limit(20) - |> schema( - %{ - users: %{table_name: "people"}, - companies: %{address: %{cardinality: :has_one}} - } - ) + |> schema(%{ + users: %{table_name: "people"}, + companies: %{address: %{cardinality: :has_one}} + }) |> unique |> to_sql - assert sql == {""" - SELECT - `u`.`id`, - `u`.`name`, - `company`.`name`, - `company.address`.`city`, - `company`.`1st_address` - FROM `people` `u` - LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` - LEFT JOIN `addresses` `company.address` ON `company.address`.`company_id` = `company`.`id` AND `company.address`.`is_current` = ? - WHERE (`u`.`id` > ?) AND (`company`.`name` LIKE ?) - GROUP BY `u`.`id` - ORDER BY `u`.`name`, `company`.`name` - LIMIT ? - """, - [1, 100, "%Engel%", 20]} + assert sql == + {""" + SELECT + `u`.`id`, + `u`.`name`, + `company`.`name`, + `company.address`.`city`, + `company`.`1st_address` + FROM `people` `u` + LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` + LEFT JOIN `addresses` `company.address` ON `company.address`.`company_id` = `company`.`id` AND `company.address`.`is_current` = ? + WHERE (`u`.`id` > ?) AND (`company`.`name` LIKE ?) + GROUP BY `u`.`id` + ORDER BY `u`.`name`, `company`.`name` + LIMIT ? + """, [1, 100, "%Engel%", 20]} end end diff --git a/test/sql_dust_test.exs b/test/sql_dust_test.exs index aedb932..ae01d00 100644 --- a/test/sql_dust_test.exs +++ b/test/sql_dust_test.exs @@ -3,24 +3,27 @@ defmodule SqlDustTest do doctest SqlDust test "only passing table" do - assert SqlDust.from("users") == {""" - SELECT `u`.* - FROM `users` `u` - """, []} + assert SqlDust.from("users") == + {""" + SELECT `u`.* + FROM `users` `u` + """, []} end test "selecting all columns" do - assert SqlDust.from("users", %{select: "*"}) == {""" - SELECT * - FROM `users` `u` - """, []} + assert SqlDust.from("users", %{select: "*"}) == + {""" + SELECT * + FROM `users` `u` + """, []} end test "selecting all columns of the base resource" do - assert SqlDust.from("users", %{select: ".*"}) == {""" - SELECT `u`.* - FROM `users` `u` - """, []} + assert SqlDust.from("users", %{select: ".*"}) == + {""" + SELECT `u`.* + FROM `users` `u` + """, []} end test "selecting columns of the base resource (passing a string)" do @@ -28,10 +31,11 @@ defmodule SqlDustTest do select: "id, first_name, last_name" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id`, `u`.`first_name`, `u`.`last_name` - FROM `users` `u` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id`, `u`.`first_name`, `u`.`last_name` + FROM `users` `u` + """, []} end test "selecting columns of the base resource (passing a list)" do @@ -39,10 +43,11 @@ defmodule SqlDustTest do select: ["id", "first_name", "last_name"] } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id`, `u`.`first_name`, `u`.`last_name` - FROM `users` `u` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id`, `u`.`first_name`, `u`.`last_name` + FROM `users` `u` + """, []} end test "selecting weirdly named columns of the base resource (passing a list)" do @@ -51,8 +56,9 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - "SELECT `u`.`id`, `u`.`first_name`, `u`.`m2`, `u`.`a`\nFROM `users` `u`\n", - []} + "SELECT `u`.`id`, `u`.`first_name`, `u`.`m2`, `u`.`a`\nFROM `users` `u`\n", + [] + } end test "selecting weirdly named columns of the base resource (passing a map)" do @@ -64,13 +70,13 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, `u`.`name`, `u`.`m2`, `u`.`a` - FROM `users` `u` - WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) - """, - ["%Paul%", "%Engel%"] - } + """ + SELECT `u`.`id`, `u`.`name`, `u`.`m2`, `u`.`a` + FROM `users` `u` + WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) + """, + ["%Paul%", "%Engel%"] + } end test "interpolating variables" do @@ -80,13 +86,13 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, CONCAT(`u`.`name`, ?) - FROM `users` `u` - """, - ["PostFix!"], - ~w(postfix) - } + """ + SELECT `u`.`id`, CONCAT(`u`.`name`, ?) + FROM `users` `u` + """, + ["PostFix!"], + ~w(postfix) + } end test "interpolating variables respects multiple occurrences " do @@ -97,14 +103,14 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, CONCAT(`u`.`name`, ?) - FROM `users` `u` - WHERE (`u`.`foobar` LIKE ?) - """, - ["PostFix!", "PostFix!"], - ~w(postfix postfix) - } + """ + SELECT `u`.`id`, CONCAT(`u`.`name`, ?) + FROM `users` `u` + WHERE (`u`.`foobar` LIKE ?) + """, + ["PostFix!", "PostFix!"], + ~w(postfix postfix) + } end test "interpolating variables containing nested maps" do @@ -115,14 +121,14 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, `u`.`name` - FROM `users` `u` - WHERE (`u`.`first_name` LIKE ?) - """, - ["Paul"], - ~w(user.first_name) - } + """ + SELECT `u`.`id`, `u`.`name` + FROM `users` `u` + WHERE (`u`.`first_name` LIKE ?) + """, + ["Paul"], + ~w(user.first_name) + } end test "resulting variables respects multiple occurrences " do @@ -133,14 +139,14 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, CONCAT(`u`.`name`, ?) - FROM `users` `u` - WHERE (`u`.`foobar` LIKE ?) - """, - ["PostFix!", "PostFix!"], - ~w(postfix postfix) - } + """ + SELECT `u`.`id`, CONCAT(`u`.`name`, ?) + FROM `users` `u` + WHERE (`u`.`foobar` LIKE ?) + """, + ["PostFix!", "PostFix!"], + ~w(postfix postfix) + } end test "using functions" do @@ -148,24 +154,27 @@ defmodule SqlDustTest do select: "COUNT(*)" } - assert SqlDust.from("users", options) == {""" - SELECT COUNT(*) - FROM `users` `u` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT COUNT(*) + FROM `users` `u` + """, []} end test "using quoted arguments" do options = %{ - select: "id, CONCAT(\"First name: '\", first_name, \"' Last name: '\", last_name, \"'\"), DATE_FORMAT(updated_at, '%d-%m-%Y')" - } - - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id`, - CONCAT("First name: '", `u`.`first_name`, "' Last name: '", `u`.`last_name`, "'"), - DATE_FORMAT(`u`.`updated_at`, '%d-%m-%Y') - FROM `users` `u` - """, []} + select: + "id, CONCAT(\"First name: '\", first_name, \"' Last name: '\", last_name, \"'\"), DATE_FORMAT(updated_at, '%d-%m-%Y')" + } + + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id`, + CONCAT("First name: '", `u`.`first_name`, "' Last name: '", `u`.`last_name`, "'"), + DATE_FORMAT(`u`.`updated_at`, '%d-%m-%Y') + FROM `users` `u` + """, []} end test "selecting columns of a 'belongs to' association" do @@ -173,17 +182,18 @@ defmodule SqlDustTest do select: "id, first_name, user_role.name, department.id, department.name" } - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `user_role`.`name`, - `department`.`id`, - `department`.`name` - FROM `users` `u` - LEFT JOIN `user_roles` `user_role` ON `user_role`.`id` = `u`.`user_role_id` - LEFT JOIN `departments` `department` ON `department`.`id` = `u`.`department_id` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `user_role`.`name`, + `department`.`id`, + `department`.`name` + FROM `users` `u` + LEFT JOIN `user_roles` `user_role` ON `user_role`.`id` = `u`.`user_role_id` + LEFT JOIN `departments` `department` ON `department`.`id` = `u`.`department_id` + """, []} end test "extreme column and table naming with a belongs_to" do @@ -202,17 +212,18 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `user_role`.`name`, - `department`.`id`, - `department`.`name` - FROM `users` `u` - LEFT JOIN `user_roles` `user_role` ON `user_role`.`id` = `u`.`user_role_id` - LEFT JOIN `super department.store` `department` ON `department`.`id` = `u`.`insane weird.stuff` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `user_role`.`name`, + `department`.`id`, + `department`.`name` + FROM `users` `u` + LEFT JOIN `user_roles` `user_role` ON `user_role`.`id` = `u`.`user_role_id` + LEFT JOIN `super department.store` `department` ON `department`.`id` = `u`.`insane weird.stuff` + """, []} end test "selecting columns of a nested 'belongs to' association" do @@ -220,15 +231,16 @@ defmodule SqlDustTest do select: "id, first_name, company.category.name" } - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `company.category`.`name` - FROM `users` `u` - LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` - LEFT JOIN `categories` `company.category` ON `company.category`.`id` = `company`.`category_id` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `company.category`.`name` + FROM `users` `u` + LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` + LEFT JOIN `categories` `company.category` ON `company.category`.`id` = `company`.`category_id` + """, []} end test "selecting columns of a 'has many' association" do @@ -236,15 +248,16 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(orders.id)" } - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`orders`.`id`) - FROM `users` `u` - LEFT JOIN `orders` `orders` ON `orders`.`user_id` = `u`.`id` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`orders`.`id`) + FROM `users` `u` + LEFT JOIN `orders` `orders` ON `orders`.`user_id` = `u`.`id` + """, []} end test "selecting columns of a nested 'has many' association" do @@ -252,22 +265,24 @@ defmodule SqlDustTest do select: "id, first_name, last_name, GROUP_CONCAT(company.orders.id)" } - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`company.orders`.`id`) - FROM `users` `u` - LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` - LEFT JOIN `orders` `company.orders` ON `company.orders`.`company_id` = `company`.`id` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`company.orders`.`id`) + FROM `users` `u` + LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` + LEFT JOIN `orders` `company.orders` ON `company.orders`.`company_id` = `company`.`id` + """, []} end test "selecting columns of a 'has and belongs to many' association" do options = %{ select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } + schema = %{ users: %{ skills: %{ @@ -276,16 +291,17 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`skills`.`name`) - FROM `users` `u` - LEFT JOIN `skills_users` `skills_bridge_table` ON `skills_bridge_table`.`user_id` = `u`.`id` - LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`skills`.`name`) + FROM `users` `u` + LEFT JOIN `skills_users` `skills_bridge_table` ON `skills_bridge_table`.`user_id` = `u`.`id` + LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` + """, []} end test "selecting columns of a 'has one' association" do @@ -293,6 +309,7 @@ defmodule SqlDustTest do select: "id, name, current_price.amount", group_by: "id" } + schema = %{ products: %{ current_price: %{ @@ -302,15 +319,16 @@ defmodule SqlDustTest do } } - assert SqlDust.from("products", options, schema) == {""" - SELECT - `p`.`id`, - `p`.`name`, - `current_price`.`amount` - FROM `products` `p` - LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` - GROUP BY `p`.`id` - """, []} + assert SqlDust.from("products", options, schema) == + {""" + SELECT + `p`.`id`, + `p`.`name`, + `current_price`.`amount` + FROM `products` `p` + LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` + GROUP BY `p`.`id` + """, []} end test "adding join conditions for paths" do @@ -318,6 +336,7 @@ defmodule SqlDustTest do select: "id, name, current_price.amount", join_on: "current_price.latest = 1" } + schema = %{ products: %{ current_price: %{ @@ -327,20 +346,22 @@ defmodule SqlDustTest do } } - assert SqlDust.from("products", options, schema) == {""" - SELECT - `p`.`id`, - `p`.`name`, - `current_price`.`amount` - FROM `products` `p` - LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` AND `current_price`.`latest` = 1 - """, []} + assert SqlDust.from("products", options, schema) == + {""" + SELECT + `p`.`id`, + `p`.`name`, + `current_price`.`amount` + FROM `products` `p` + LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` AND `current_price`.`latest` = 1 + """, []} end test "adding join conditions within the schema" do options = %{ select: "id, name, current_price.amount" } + schema = %{ products: %{ current_price: %{ @@ -351,14 +372,15 @@ defmodule SqlDustTest do } } - assert SqlDust.from("products", options, schema) == {""" - SELECT - `p`.`id`, - `p`.`name`, - `current_price`.`amount` - FROM `products` `p` - LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` AND `current_price`.`latest` = 1 - """, []} + assert SqlDust.from("products", options, schema) == + {""" + SELECT + `p`.`id`, + `p`.`name`, + `current_price`.`amount` + FROM `products` `p` + LEFT JOIN `prices` `current_price` ON `current_price`.`product_id` = `p`.`id` AND `current_price`.`latest` = 1 + """, []} end test "adding join conditions within the schema using variables" do @@ -368,6 +390,7 @@ defmodule SqlDustTest do scope: "awesome_scope" } } + schema = %{ products: %{ current_statistic: %{ @@ -379,23 +402,24 @@ defmodule SqlDustTest do } assert SqlDust.from("products", options, schema) == { - """ - SELECT - `p`.`id`, - `p`.`name`, - `current_statistic`.`amount` - FROM `products` `p` - LEFT JOIN `statistics` `current_statistic` ON `current_statistic`.`product_id` = `p`.`id` AND `current_statistic`.`scope` = ? - """, - ["awesome_scope"], - ~w(scope) - } + """ + SELECT + `p`.`id`, + `p`.`name`, + `current_statistic`.`amount` + FROM `products` `p` + LEFT JOIN `statistics` `current_statistic` ON `current_statistic`.`product_id` = `p`.`id` AND `current_statistic`.`scope` = ? + """, + ["awesome_scope"], + ~w(scope) + } end test "selecting columns of a nested 'has and belongs to many' association" do options = %{ select: "id, first_name, last_name, GROUP_CONCAT(company.tags.name)" } + schema = %{ companies: %{ tags: %{ @@ -404,17 +428,18 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`company.tags`.`name`) - FROM `users` `u` - LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` - LEFT JOIN `companies_tags` `company.tags_bridge_table` ON `company.tags_bridge_table`.`company_id` = `company`.`id` - LEFT JOIN `tags` `company.tags` ON `company.tags`.`id` = `company.tags_bridge_table`.`tag_id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`company.tags`.`name`) + FROM `users` `u` + LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` + LEFT JOIN `companies_tags` `company.tags_bridge_table` ON `company.tags_bridge_table`.`company_id` = `company`.`id` + LEFT JOIN `tags` `company.tags` ON `company.tags`.`id` = `company.tags_bridge_table`.`tag_id` + """, []} end test "supports ['path = ? OR path = ?', criteria1, criteria2] notation (complexity 1)" do @@ -424,13 +449,13 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, `u`.`name` - FROM `users` `u` - WHERE (`u`.`name` LIKE ?) - """, - ["%Engel%"] - } + """ + SELECT `u`.`id`, `u`.`name` + FROM `users` `u` + WHERE (`u`.`name` LIKE ?) + """, + ["%Engel%"] + } end test "supports ['path = ? OR path = ?', criteria1, criteria2] notation (complexity 2)" do @@ -440,13 +465,13 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, `u`.`name` - FROM `users` `u` - WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) - """, - ["%Paul%", "%Engel%"] - } + """ + SELECT `u`.`id`, `u`.`name` + FROM `users` `u` + WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) + """, + ["%Paul%", "%Engel%"] + } end test "supports ['path = ? OR path = ?', criteria1, criteria2] notation (complexity 3)" do @@ -461,16 +486,16 @@ defmodule SqlDustTest do } assert SqlDust.from("users", options) == { - """ - SELECT `u`.`id`, `u`.`name` - FROM `users` `u` - LEFT JOIN `addresses` `address` ON `address`.`id` = `u`.`address_id` - LEFT JOIN `subscriptions` `subscription` ON `subscription`.`id` = `u`.`subscription_id` - LEFT JOIN `tags` `tags` ON `tags`.`user_id` = `u`.`id` - WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) AND (`u`.`company_id` = 1982) AND (`address`.`city` = ? AND `subscription`.`active` = ?) AND (`tags`.`name` IN (?)) - """, - ["%Paul%", "%Engel%", "Amsterdam", true, [1, 8, 1982]] - } + """ + SELECT `u`.`id`, `u`.`name` + FROM `users` `u` + LEFT JOIN `addresses` `address` ON `address`.`id` = `u`.`address_id` + LEFT JOIN `subscriptions` `subscription` ON `subscription`.`id` = `u`.`subscription_id` + LEFT JOIN `tags` `tags` ON `tags`.`user_id` = `u`.`id` + WHERE (`u`.`name` LIKE ? OR `u`.`name` LIKE ?) AND (`u`.`company_id` = 1982) AND (`address`.`city` = ? AND `subscription`.`active` = ?) AND (`tags`.`name` IN (?)) + """, + ["%Paul%", "%Engel%", "Amsterdam", true, [1, 8, 1982]] + } end test "overriding the resource table name" do @@ -480,56 +505,62 @@ defmodule SqlDustTest do } } - assert SqlDust.from("resellers", %{}, schema) == {""" - SELECT `r`.* - FROM `companies` `r` - """, []} + assert SqlDust.from("resellers", %{}, schema) == + {""" + SELECT `r`.* + FROM `companies` `r` + """, []} end test "overriding the resource of an association" do options = %{ select: ["id", "description", "CONCAT(assignee.first_name, ' ', assignee.last_name)"] } + schema = %{ issues: %{ assignee: %{resource: "users"} } } - assert SqlDust.from("issues", options, schema) == {""" - SELECT - `i`.`id`, - `i`.`description`, - CONCAT(`assignee`.`first_name`, ' ', `assignee`.`last_name`) - FROM `issues` `i` - LEFT JOIN `users` `assignee` ON `assignee`.`id` = `i`.`assignee_id` - """, []} + assert SqlDust.from("issues", options, schema) == + {""" + SELECT + `i`.`id`, + `i`.`description`, + CONCAT(`assignee`.`first_name`, ' ', `assignee`.`last_name`) + FROM `issues` `i` + LEFT JOIN `users` `assignee` ON `assignee`.`id` = `i`.`assignee_id` + """, []} end test "overriding the table name of an association" do options = %{ select: ["id", "description", "CONCAT(assignee.first_name, ' ', assignee.last_name)"] } + schema = %{ issues: %{ assignee: %{table_name: "users"} } } - assert SqlDust.from("issues", options, schema) == {""" - SELECT - `i`.`id`, - `i`.`description`, - CONCAT(`assignee`.`first_name`, ' ', `assignee`.`last_name`) - FROM `issues` `i` - LEFT JOIN `users` `assignee` ON `assignee`.`id` = `i`.`assignee_id` - """, []} + assert SqlDust.from("issues", options, schema) == + {""" + SELECT + `i`.`id`, + `i`.`description`, + CONCAT(`assignee`.`first_name`, ' ', `assignee`.`last_name`) + FROM `issues` `i` + LEFT JOIN `users` `assignee` ON `assignee`.`id` = `i`.`assignee_id` + """, []} end test "overriding the bridge table of a has and belongs to many association" do options = %{ select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } + schema = %{ users: %{ skills: %{ @@ -540,22 +571,24 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`skills`.`name`) - FROM `users` `u` - LEFT JOIN `skill_set` `skills_bridge_table` ON `skills_bridge_table`.`person_id` = `u`.`id` - LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`skills`.`name`) + FROM `users` `u` + LEFT JOIN `skill_set` `skills_bridge_table` ON `skills_bridge_table`.`person_id` = `u`.`id` + LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` + """, []} end test "overriding the bridge table of a has and belongs to many with extreme table names in association" do options = %{ select: "id, first_name, last_name, GROUP_CONCAT(skills.name)" } + schema = %{ users: %{ skills: %{ @@ -566,16 +599,17 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT - `u`.`id`, - `u`.`first_name`, - `u`.`last_name`, - GROUP_CONCAT(`skills`.`name`) - FROM `users` `u` - LEFT JOIN `test skill_set.awesome` `skills_bridge_table` ON `skills_bridge_table`.`strange.person_id` = `u`.`id` - LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT + `u`.`id`, + `u`.`first_name`, + `u`.`last_name`, + GROUP_CONCAT(`skills`.`name`) + FROM `users` `u` + LEFT JOIN `test skill_set.awesome` `skills_bridge_table` ON `skills_bridge_table`.`strange.person_id` = `u`.`id` + LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` + """, []} end test "grouping the query result" do @@ -584,12 +618,13 @@ defmodule SqlDustTest do group_by: "category.name" } - assert SqlDust.from("users", options) == {""" - SELECT COUNT(*) - FROM `users` `u` - LEFT JOIN `categories` `category` ON `category`.`id` = `u`.`category_id` - GROUP BY `category`.`name` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT COUNT(*) + FROM `users` `u` + LEFT JOIN `categories` `category` ON `category`.`id` = `u`.`category_id` + GROUP BY `category`.`name` + """, []} end test "ordering the query result (passing a string)" do @@ -598,11 +633,12 @@ defmodule SqlDustTest do order_by: "last_name ASC, first_name" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - ORDER BY `u`.`last_name` ASC, `u`.`first_name` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + ORDER BY `u`.`last_name` ASC, `u`.`first_name` + """, []} end test "ordering the query result (passing a list)" do @@ -611,11 +647,12 @@ defmodule SqlDustTest do order_by: ["last_name ASC", "first_name"] } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - ORDER BY `u`.`last_name` ASC, `u`.`first_name` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + ORDER BY `u`.`last_name` ASC, `u`.`first_name` + """, []} end test "limiting the query result" do @@ -623,11 +660,12 @@ defmodule SqlDustTest do limit: 20 } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - 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 @@ -640,13 +678,12 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - LIMIT ? - """, - [20], - ~w(_options_.limit)} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + LIMIT ? + """, [20], ~w(_options_.limit)} end test "adding an offset to the query result" do @@ -655,12 +692,13 @@ defmodule SqlDustTest do offset: 20 } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - LIMIT ? - OFFSET ? - """, [10, 20]} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + LIMIT ? + OFFSET ? + """, [10, 20]} end test "adding an offset to the query result by passing the offset in variables[:_options_][:offset]" do @@ -674,79 +712,84 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - LIMIT ? - OFFSET ? - """, - [10, 20], - [nil, "_options_.offset"]} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + LIMIT ? + OFFSET ? + """, [10, 20], [nil, "_options_.offset"]} end test "quoting SELECT statement aliases" do options = %{ - select: "id AS foo.bar", + select: "id AS foo.bar" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id` AS `foo.bar` - FROM `users` `u` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id` AS `foo.bar` + FROM `users` `u` + """, []} end test "putting quoted SELECT statement aliases either in the WHERE or HAVING statement" do options = %{ - select: "id AS id, CONCAT(first_name, ' ', last_name) AS name, company.name AS company.name", + select: + "id AS id, CONCAT(first_name, ' ', last_name) AS name, company.name AS company.name", where: ["id <= 1982", "name LIKE '%Engel%'", "company.name LIKE '%Inc%'"] } - assert SqlDust.from("users", options) == {""" - SELECT - `u`.`id` AS `id`, - CONCAT(`u`.`first_name`, ' ', `u`.`last_name`) AS `name`, - `company`.`name` AS `company.name` - FROM `users` `u` - LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` - WHERE (`u`.`id` <= 1982) AND (`company`.`name` LIKE '%Inc%') - HAVING (`name` LIKE '%Engel%') - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT + `u`.`id` AS `id`, + CONCAT(`u`.`first_name`, ' ', `u`.`last_name`) AS `name`, + `company`.`name` AS `company.name` + FROM `users` `u` + LEFT JOIN `companies` `company` ON `company`.`id` = `u`.`company_id` + WHERE (`u`.`id` <= 1982) AND (`company`.`name` LIKE '%Inc%') + HAVING (`name` LIKE '%Engel%') + """, []} end test "respecting preserved word NULL" do options = %{ - where: "name IS NOT NULL", + where: "name IS NOT NULL" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - WHERE (`u`.`name` IS NOT NULL) - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + WHERE (`u`.`name` IS NOT NULL) + """, []} end test "respecting booleans" do options = %{ - where: "is_admin = true OR FALSE", + where: "is_admin = true OR FALSE" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - WHERE (`u`.`is_admin` = true OR FALSE) - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + WHERE (`u`.`is_admin` = true OR FALSE) + """, []} end test "handling '' within WHERE statements" do options = %{ - where: "name = ''", + where: "name = ''" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.* - FROM `users` `u` - WHERE (`u`.`name` = '') - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.* + FROM `users` `u` + WHERE (`u`.`name` = '') + """, []} end test "not auto-grouping base records at default" do @@ -755,13 +798,14 @@ defmodule SqlDustTest do where: "organization.addresses.street LIKE '%Broad%'" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` - LEFT JOIN `addresses` `organization.addresses` ON `organization.addresses`.`organization_id` = `organization`.`id` - WHERE (`organization.addresses`.`street` LIKE '%Broad%') - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` + LEFT JOIN `addresses` `organization.addresses` ON `organization.addresses`.`organization_id` = `organization`.`id` + WHERE (`organization.addresses`.`street` LIKE '%Broad%') + """, []} end test "being able to ensuring unique base records for has_many associations" do @@ -771,14 +815,15 @@ defmodule SqlDustTest do unique: true } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` - LEFT JOIN `addresses` `organization.addresses` ON `organization.addresses`.`organization_id` = `organization`.`id` - WHERE (`organization.addresses`.`street` LIKE '%Broad%') - GROUP BY `u`.`id` - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` + LEFT JOIN `addresses` `organization.addresses` ON `organization.addresses`.`organization_id` = `organization`.`id` + WHERE (`organization.addresses`.`street` LIKE '%Broad%') + GROUP BY `u`.`id` + """, []} end test "extreme column and table naming with has_many" do @@ -799,14 +844,15 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` - LEFT JOIN `some address.table` `organization.addresses` ON `organization.addresses`.`sql dust.is.cool` = `organization`.`id` - WHERE (`organization.addresses`.`street` LIKE '%Broad%') - GROUP BY `u`.`id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `organizations` `organization` ON `organization`.`id` = `u`.`organization_id` + LEFT JOIN `some address.table` `organization.addresses` ON `organization.addresses`.`sql dust.is.cool` = `organization`.`id` + WHERE (`organization.addresses`.`street` LIKE '%Broad%') + GROUP BY `u`.`id` + """, []} end test "being able to ensuring unique base records for has_one associations" do @@ -824,13 +870,14 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `organizations` `organization` ON `organization`.`user_id` = `u`.`id` - WHERE (`organization`.`name` LIKE '%Broad%') - GROUP BY `u`.`id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `organizations` `organization` ON `organization`.`user_id` = `u`.`id` + WHERE (`organization`.`name` LIKE '%Broad%') + GROUP BY `u`.`id` + """, []} end test "extreme column and table naming with a has_one" do @@ -852,13 +899,14 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `organization hallo.test` `organization` ON `organization`.`user.organization column` = `u`.`id` - WHERE (`organization`.`name` LIKE '%Broad%') - GROUP BY `u`.`id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `organization hallo.test` `organization` ON `organization`.`user.organization column` = `u`.`id` + WHERE (`organization`.`name` LIKE '%Broad%') + GROUP BY `u`.`id` + """, []} end test "being able to ensuring unique base records for has_and_belongs_to_many associations" do @@ -876,24 +924,27 @@ defmodule SqlDustTest do } } - assert SqlDust.from("users", options, schema) == {""" - SELECT `u`.`id` - FROM `users` `u` - LEFT JOIN `skills_users` `skills_bridge_table` ON `skills_bridge_table`.`user_id` = `u`.`id` - LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` - WHERE (`skills`.`name` LIKE '%cool%') - GROUP BY `u`.`id` - """, []} + assert SqlDust.from("users", options, schema) == + {""" + SELECT `u`.`id` + FROM `users` `u` + LEFT JOIN `skills_users` `skills_bridge_table` ON `skills_bridge_table`.`user_id` = `u`.`id` + LEFT JOIN `skills` `skills` ON `skills`.`id` = `skills_bridge_table`.`skill_id` + WHERE (`skills`.`name` LIKE '%cool%') + GROUP BY `u`.`id` + """, []} end test "DirectiveRecord example 1 (with additional WHERE statements)" do options = %{ - select: "id, name, COUNT(orders.id) AS order.count, GROUP_CONCAT(DISTINCT tags.name) AS tags, foo.tags", + select: + "id, name, COUNT(orders.id) AS order.count, GROUP_CONCAT(DISTINCT tags.name) AS tags, foo.tags", group_by: "id", where: ["name LIKE '%Paul%'", "order.count > 5", "foo.tags = 1"], order_by: "COUNT(DISTINCT tags.id) DESC", limit: 5 } + schema = %{ customers: %{ tags: %{ @@ -902,30 +953,32 @@ defmodule SqlDustTest do } } - assert SqlDust.from("customers", options, schema) == {""" - SELECT - `c`.`id`, - `c`.`name`, - COUNT(`orders`.`id`) AS `order.count`, - GROUP_CONCAT(DISTINCT `tags`.`name`) AS `tags`, - `foo`.`tags` - FROM `customers` `c` - LEFT JOIN `orders` `orders` ON `orders`.`customer_id` = `c`.`id` - LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` - LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` - LEFT JOIN `foos` `foo` ON `foo`.`id` = `c`.`foo_id` - WHERE (`c`.`name` LIKE '%Paul%') AND (`foo`.`tags` = 1) - GROUP BY `c`.`id` - HAVING (`order.count` > 5) - ORDER BY COUNT(DISTINCT `tags`.`id`) DESC - LIMIT ? - """, [5]} + assert SqlDust.from("customers", options, schema) == + {""" + SELECT + `c`.`id`, + `c`.`name`, + COUNT(`orders`.`id`) AS `order.count`, + GROUP_CONCAT(DISTINCT `tags`.`name`) AS `tags`, + `foo`.`tags` + FROM `customers` `c` + LEFT JOIN `orders` `orders` ON `orders`.`customer_id` = `c`.`id` + LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` + LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` + LEFT JOIN `foos` `foo` ON `foo`.`id` = `c`.`foo_id` + WHERE (`c`.`name` LIKE '%Paul%') AND (`foo`.`tags` = 1) + GROUP BY `c`.`id` + HAVING (`order.count` > 5) + ORDER BY COUNT(DISTINCT `tags`.`id`) DESC + LIMIT ? + """, [5]} end test "DirectiveRecord example 3" do options = %{ where: "tags.name LIKE '%gifts%'" } + schema = %{ customers: %{ tags: %{ @@ -934,13 +987,14 @@ defmodule SqlDustTest do } } - assert SqlDust.from("customers", options, schema) == {""" - SELECT `c`.* - FROM `customers` `c` - LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` - LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` - WHERE (`tags`.`name` LIKE '%gifts%') - """, []} + assert SqlDust.from("customers", options, schema) == + {""" + SELECT `c`.* + FROM `customers` `c` + LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` + LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` + WHERE (`tags`.`name` LIKE '%gifts%') + """, []} end test "DirectiveRecord example 5" do @@ -949,6 +1003,7 @@ defmodule SqlDustTest do where: "tags.name LIKE '%gifts%'", group_by: "tags.id" } + schema = %{ customers: %{ tags: %{ @@ -957,14 +1012,15 @@ defmodule SqlDustTest do } } - assert SqlDust.from("customers", options, schema) == {""" - SELECT `tags`.* - FROM `customers` `c` - LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` - LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` - WHERE (`tags`.`name` LIKE '%gifts%') - GROUP BY `tags`.`id` - """, []} + assert SqlDust.from("customers", options, schema) == + {""" + SELECT `tags`.* + FROM `customers` `c` + LEFT JOIN `customers_tags` `tags_bridge_table` ON `tags_bridge_table`.`customer_id` = `c`.`id` + LEFT JOIN `tags` `tags` ON `tags`.`id` = `tags_bridge_table`.`tag_id` + WHERE (`tags`.`name` LIKE '%gifts%') + GROUP BY `tags`.`id` + """, []} end test "DirectiveRecord example 6" do @@ -974,16 +1030,17 @@ defmodule SqlDustTest do group_by: "id" } - assert SqlDust.from("customers", options) == {""" - SELECT - `c`.`id`, - `c`.`name`, - COUNT(`orders`.`id`) AS `order_count` - FROM `customers` `c` - LEFT JOIN `orders` `orders` ON `orders`.`customer_id` = `c`.`id` - GROUP BY `c`.`id` - HAVING (`order_count` > 3) - """, []} + assert SqlDust.from("customers", options) == + {""" + SELECT + `c`.`id`, + `c`.`name`, + COUNT(`orders`.`id`) AS `order_count` + FROM `customers` `c` + LEFT JOIN `orders` `orders` ON `orders`.`customer_id` = `c`.`id` + GROUP BY `c`.`id` + HAVING (`order_count` > 3) + """, []} end test "prepending path aliases in the HAVING statement while respecting SELECT statement aliases" do @@ -993,12 +1050,13 @@ defmodule SqlDustTest do order_by: "description desc" } - assert SqlDust.from("users", options) == {""" - SELECT `u`.`id` AS `identifier` - FROM `users` `u` - HAVING (`identifier` > 0 AND `u`.`id` != 2) - ORDER BY `u`.`description` desc - """, []} + assert SqlDust.from("users", options) == + {""" + SELECT `u`.`id` AS `identifier` + FROM `users` `u` + HAVING (`identifier` > 0 AND `u`.`id` != 2) + ORDER BY `u`.`description` desc + """, []} end test "downcasing base table alias" do @@ -1008,24 +1066,25 @@ defmodule SqlDustTest do } } - assert SqlDust.from("User", %{}, schema) == {""" - SELECT `u`.* - FROM `people` `u` - """, []} + assert SqlDust.from("User", %{}, schema) == + {""" + SELECT `u`.* + FROM `people` `u` + """, []} end test "ignore WHERE with empty statement" do - assert SqlDust.from("users", %{where: [""]}) == {""" - SELECT `u`.* - FROM `users` `u` - """, [] - } - - assert SqlDust.from("users", %{where: ["", "id = 1", " "]}) == {""" - SELECT `u`.* - FROM `users` `u` - WHERE (`u`.`id` = 1) - """, [] - } + assert SqlDust.from("users", %{where: [""]}) == + {""" + SELECT `u`.* + FROM `users` `u` + """, []} + + assert SqlDust.from("users", %{where: ["", "id = 1", " "]}) == + {""" + SELECT `u`.* + FROM `users` `u` + WHERE (`u`.`id` = 1) + """, []} end end From e4c411f3d4c7bc3ee37b0d6bab4e3d7bf489ad1c Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:33:32 +0200 Subject: [PATCH 5/6] Add GitHub actions --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..50f92ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: Elixir CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup elixir + uses: actions/setup-elixir@v1 + with: + elixir-version: 1.9.4 # Define the elixir version [required] + otp-version: 22.2 # Define the OTP version [required] + - name: Install Dependencies + run: mix deps.get + - name: Run Tests + run: mix test From 3c24e27eb0e18d0e6b50d8caa4e3f3189b985200 Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 22 Sep 2021 00:36:47 +0200 Subject: [PATCH 6/6] Adjust Github actions --- .github/workflows/ci.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50f92ec..a08e920 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,16 +8,19 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - name: Setup elixir - uses: actions/setup-elixir@v1 + - uses: erlef/setup-elixir@v1 with: - elixir-version: 1.9.4 # Define the elixir version [required] - otp-version: 22.2 # Define the OTP version [required] - - name: Install Dependencies - run: mix deps.get - - name: Run Tests - run: mix test + otp-version: "24" + elixir-version: "1.12" + + - uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix- + - run: mix deps.get + - run: mix test