Skip to content

Commit 8dc5a77

Browse files
authored
Add support for index directions (#657)
1 parent 45fd270 commit 8dc5a77

File tree

8 files changed

+177
-12
lines changed

8 files changed

+177
-12
lines changed

lib/ecto/adapters/myxql/connection.ex

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,11 +1312,34 @@ if Code.ensure_loaded?(MyXQL) do
13121312
defp default_expr(:error),
13131313
do: []
13141314

1315+
defp index_expr({dir, literal})
1316+
when is_binary(literal),
1317+
do: index_dir(dir, literal)
1318+
1319+
defp index_expr({dir, literal}),
1320+
do: index_dir(dir, quote_name(literal))
1321+
13151322
defp index_expr(literal) when is_binary(literal),
13161323
do: literal
13171324

13181325
defp index_expr(literal), do: quote_name(literal)
13191326

1327+
defp index_dir(dir, str)
1328+
when dir in [
1329+
:asc,
1330+
:asc_nulls_first,
1331+
:asc_nulls_last,
1332+
:desc,
1333+
:desc_nulls_first,
1334+
:desc_nulls_last
1335+
] do
1336+
case dir do
1337+
:asc -> [str | " ASC"]
1338+
:desc -> [str | " DESC"]
1339+
_ -> error!(nil, "#{dir} is not supported in indexes in MySQL")
1340+
end
1341+
end
1342+
13201343
defp engine_expr(storage_engine),
13211344
do: [" ENGINE = ", String.upcase(to_string(storage_engine || "INNODB"))]
13221345

lib/ecto/adapters/postgres/connection.ex

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1305,7 +1305,7 @@ if Code.ensure_loaded?(Postgrex) do
13051305

13061306
def execute_ddl({command, %Index{} = index}) when command in @creates do
13071307
fields = Enum.map_intersperse(index.columns, ", ", &index_expr/1)
1308-
include_fields = Enum.map_intersperse(index.include, ", ", &index_expr/1)
1308+
include_fields = Enum.map_intersperse(index.include, ", ", &include_expr/1)
13091309

13101310
maybe_nulls_distinct =
13111311
case index.nulls_distinct do
@@ -1704,12 +1704,43 @@ if Code.ensure_loaded?(Postgrex) do
17041704
":default may be a string, number, boolean, list of strings, list of integers, map (when type is Map), or a fragment(...)"
17051705
)
17061706

1707+
defp index_expr({dir, literal}) when is_binary(literal),
1708+
do: index_dir(dir, literal)
1709+
1710+
defp index_expr({dir, literal}),
1711+
do: index_dir(dir, quote_name(literal))
1712+
17071713
defp index_expr(literal) when is_binary(literal),
17081714
do: literal
17091715

17101716
defp index_expr(literal),
17111717
do: quote_name(literal)
17121718

1719+
defp index_dir(dir, str)
1720+
when dir in [
1721+
:asc,
1722+
:asc_nulls_first,
1723+
:asc_nulls_last,
1724+
:desc,
1725+
:desc_nulls_first,
1726+
:desc_nulls_last
1727+
] do
1728+
case dir do
1729+
:asc -> [str | " ASC"]
1730+
:asc_nulls_first -> [str | " ASC NULLS FIRST"]
1731+
:asc_nulls_last -> [str | " ASC NULLS LAST"]
1732+
:desc -> [str | " DESC"]
1733+
:desc_nulls_first -> [str | " DESC NULLS FIRST"]
1734+
:desc_nulls_last -> [str | " DESC NULLS LAST"]
1735+
end
1736+
end
1737+
1738+
defp include_expr(literal) when is_binary(literal),
1739+
do: literal
1740+
1741+
defp include_expr(literal),
1742+
do: quote_name(literal)
1743+
17131744
defp options_expr(nil),
17141745
do: []
17151746

lib/ecto/adapters/tds/connection.ex

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,7 @@ if Code.ensure_loaded?(Tds) do
11961196
include =
11971197
index.include
11981198
|> List.wrap()
1199-
|> Enum.map_intersperse(", ", &index_expr/1)
1199+
|> Enum.map_intersperse(", ", &include_expr/1)
12001200

12011201
[
12021202
[
@@ -1570,9 +1570,35 @@ if Code.ensure_loaded?(Tds) do
15701570
defp constraint_name(type, table, name),
15711571
do: quote_name("#{type}_#{table.prefix}_#{table.name}_#{name}")
15721572

1573+
defp index_expr({dir, literal})
1574+
when is_binary(literal),
1575+
do: index_dir(dir, literal)
1576+
1577+
defp index_expr({dir, literal}),
1578+
do: index_dir(dir, quote_name(literal))
1579+
15731580
defp index_expr(literal) when is_binary(literal), do: literal
15741581
defp index_expr(literal), do: quote_name(literal)
15751582

1583+
defp index_dir(dir, str)
1584+
when dir in [
1585+
:asc,
1586+
:asc_nulls_first,
1587+
:asc_nulls_last,
1588+
:desc,
1589+
:desc_nulls_first,
1590+
:desc_nulls_last
1591+
] do
1592+
case dir do
1593+
:asc -> [str | " ASC"]
1594+
:desc -> [str | " DESC"]
1595+
_ -> error!(nil, "#{dir} is not supported in indexes in Tds adapter")
1596+
end
1597+
end
1598+
1599+
defp include_expr(literal) when is_binary(literal), do: literal
1600+
defp include_expr(literal), do: quote_name(literal)
1601+
15761602
defp engine_expr(_storage_engine), do: [""]
15771603

15781604
defp options_expr(nil), do: []

lib/ecto/migration.ex

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,21 @@ defmodule Ecto.Migration do
393393
comment: nil,
394394
options: nil
395395

396+
@type column :: atom | String.t() | {index_dir(), atom | String.t()}
397+
398+
@type index_dir ::
399+
:asc
400+
| :asc_nulls_first
401+
| :asc_nulls_last
402+
| :desc
403+
| :desc_nulls_first
404+
| :desc_nulls_last
405+
396406
@type t :: %__MODULE__{
397407
table: String.t(),
398408
prefix: String.t() | nil,
399409
name: String.t() | atom,
400-
columns: [atom | String.t()],
410+
columns: [column()],
401411
unique: boolean,
402412
concurrently: boolean,
403413
using: atom | String.t(),
@@ -891,6 +901,24 @@ defmodule Ecto.Migration do
891901
create index("products", [:sku, :category_id], unique: true)
892902
create index("products", [:sku], unique: true, where: "category_id IS NULL")
893903
904+
## Sorting direction
905+
906+
You can specify the sorting direction of the index by using a keyword list:
907+
908+
create index("products", [desc: sku])
909+
910+
The following keywords are supported:
911+
912+
* `:asc`
913+
* `:asc_nulls_last`
914+
* `:asc_nulls_first`
915+
* `:desc`
916+
* `:desc_nulls_last`
917+
* `:desc_nulls_first`
918+
919+
The `*_nulls_first` and `*_nulls_last` variants are not supported by all
920+
databases.
921+
894922
## Examples
895923
896924
# With no name provided, the name of the below index defaults to
@@ -957,18 +985,19 @@ defmodule Ecto.Migration do
957985
defp default_index_name(index) do
958986
[index.table, index.columns, "index"]
959987
|> List.flatten()
960-
|> Enum.map_join(
961-
"_",
962-
fn item ->
963-
item
964-
|> to_string()
965-
|> String.replace(~r"[^\w]", "_")
966-
|> String.replace_trailing("_", "")
967-
end
968-
)
988+
|> Enum.map_join("_", &column_name/1)
969989
|> String.to_atom()
970990
end
971991

992+
defp column_name({_dir, column}), do: column_name(column)
993+
994+
defp column_name(column) do
995+
column
996+
|> to_string()
997+
|> String.replace(~r"[^\w]", "_")
998+
|> String.replace_trailing("_", "")
999+
end
1000+
9721001
@doc """
9731002
Executes arbitrary SQL, anonymous function or a keyword command.
9741003

test/ecto/adapters/myxql_test.exs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,6 +2147,25 @@ defmodule Ecto.Adapters.MyXQLTest do
21472147
]
21482148
end
21492149

2150+
test "create index with direction" do
2151+
create =
2152+
{:create, index(:posts, [:category_id, desc: :permalink])}
2153+
2154+
assert execute_ddl(create) ==
2155+
[
2156+
~s|CREATE INDEX `posts_category_id_permalink_index` ON `posts` (`category_id`, `permalink` DESC)|
2157+
]
2158+
end
2159+
2160+
test "create index with invalid direction" do
2161+
create =
2162+
{:create, index(:posts, [:category_id, asc_nulls_first: :permalink])}
2163+
2164+
assert_raise ArgumentError, "asc_nulls_first is not supported in indexes in MySQL", fn ->
2165+
execute_ddl(create)
2166+
end
2167+
end
2168+
21502169
test "create unique index" do
21512170
create = {:create, index(:posts, [:permalink], unique: true)}
21522171

test/ecto/adapters/postgres_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2700,6 +2700,16 @@ defmodule Ecto.Adapters.PostgresTest do
27002700
]
27012701
end
27022702

2703+
test "create index with direction" do
2704+
create =
2705+
{:create, index(:posts, [:category_id, desc_nulls_last: :permalink])}
2706+
2707+
assert execute_ddl(create) ==
2708+
[
2709+
~s|CREATE INDEX "posts_category_id_permalink_index" ON "posts" ("category_id", "permalink" DESC NULLS LAST)|
2710+
]
2711+
end
2712+
27032713
test "create unique index" do
27042714
create = {:create, index(:posts, [:permalink], unique: true)}
27052715

test/ecto/adapters/tds_test.exs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,27 @@ defmodule Ecto.Adapters.TdsTest do
18561856
[~s|CREATE INDEX [posts$main] ON [posts] ([permalink]) WITH(ONLINE=ON);|]
18571857
end
18581858

1859+
test "create index with direction" do
1860+
create =
1861+
{:create, index(:posts, [:category_id, desc: :permalink])}
1862+
1863+
assert execute_ddl(create) ==
1864+
[
1865+
~s|CREATE INDEX [posts_category_id_permalink_index] ON [posts] ([category_id], [permalink] DESC);|
1866+
]
1867+
end
1868+
1869+
test "create index with invalid direction" do
1870+
create =
1871+
{:create, index(:posts, [:category_id, asc_nulls_first: :permalink])}
1872+
1873+
assert_raise ArgumentError,
1874+
"asc_nulls_first is not supported in indexes in Tds adapter",
1875+
fn ->
1876+
execute_ddl(create)
1877+
end
1878+
end
1879+
18591880
test "create unique index" do
18601881
create = {:create, index(:posts, [:permalink], unique: true)}
18611882

test/ecto/migration_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,12 @@ defmodule Ecto.MigrationTest do
592592
assert {:create, %Index{}} = last_command()
593593
end
594594

595+
test "creates an index with desc_nulls_last" do
596+
create index(:posts, desc_nulls_last: :title)
597+
flush()
598+
assert {:create, %Index{}} = last_command()
599+
end
600+
595601
test "creates a check constraint" do
596602
create constraint(:posts, :price, check: "price > 0")
597603
flush()

0 commit comments

Comments
 (0)