-
Notifications
You must be signed in to change notification settings - Fork 92
/
Copy pathcustom_index.ex
155 lines (141 loc) · 3.8 KB
/
custom_index.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
defmodule AshPostgres.CustomIndex do
@moduledoc "Represents a custom index on the table backing a resource"
@fields [
:table,
:fields,
:error_fields,
:name,
:unique,
:concurrently,
:using,
:prefix,
:where,
:include,
:nulls_distinct,
:message,
:all_tenants?
]
defstruct @fields
def fields, do: @fields
@schema [
fields: [
type: {:wrap_list, {:or, [:atom, :string]}},
doc: "The fields to include in the index."
],
error_fields: [
type: {:list, :atom},
doc: "The fields to attach the error to."
],
name: [
type: :string,
doc: "the name of the index. Defaults to \"\#\{table\}_\#\{column\}_index\"."
],
unique: [
type: :boolean,
doc: "indicates whether the index should be unique.",
default: false
],
concurrently: [
type: :boolean,
doc: "indicates whether the index should be created/dropped concurrently.",
default: false
],
using: [
type: :string,
doc: "configures the index type."
],
prefix: [
type: :string,
doc: "specify an optional prefix for the index."
],
where: [
type: :string,
doc: "specify conditions for a partial index."
],
include: [
type: {:list, :string},
doc:
"specify fields for a covering index. This is not supported by all databases. For more information on PostgreSQL support, please read the official docs."
],
nulls_distinct: [
type: :boolean,
doc:
"specify whether null values should be considered distinct for a unique index. Requires PostgreSQL 15 or later",
default: true
],
message: [
type: :string,
doc: "A custom message to use for unique indexes that have been violated"
],
all_tenants?: [
type: :boolean,
default: false,
doc: "Whether or not the index should factor in the multitenancy attribute or not."
]
]
def schema, do: @schema
def transform(index) do
with {:ok, index} <- set_name(index) do
set_error_fields(index)
end
end
# sobelow_skip ["DOS.StringToAtom"]
defp set_error_fields(index) do
if index.error_fields do
{:ok, index}
else
{:ok,
%{
index
| error_fields:
Enum.flat_map(index.fields, fn field ->
if Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field)) do
if is_binary(field) do
[String.to_atom(field)]
else
[field]
end
else
[]
end
end)
}}
end
end
defp set_name(index) do
cond do
index.name ->
if Regex.match?(~r/^[0-9a-zA-Z_]+$/, index.name) do
{:ok, index}
else
{:error,
"Custom index name #{index.name} is not valid. Must have letters, numbers and underscores only"}
end
mismatched_field =
Enum.find(index.fields, fn field ->
!Regex.match?(~r/^[0-9a-zA-Z_]+$/, to_string(field))
end) ->
{:error,
"""
Custom index field #{mismatched_field} contains invalid index name characters.
A name must be set manually, i.e
`name: "your_desired_index_name"`
Index names must have letters, numbers and underscores only
"""}
true ->
{:ok, index}
end
end
def name(_resource, %{name: name}) when is_binary(name) do
name
end
# sobelow_skip ["DOS.StringToAtom"]
def name(table, %{fields: fields}) do
[table, fields, "index"]
|> List.flatten()
|> Enum.map(&to_string(&1))
|> Enum.map(&String.replace(&1, ~r"[^\w_]", "_"))
|> Enum.map_join("_", &String.replace_trailing(&1, "_", ""))
|> String.to_atom()
end
end