1 module dorm.types.patches;
2 
3 import dorm.api.db;
4 import dorm.declarative.conversion;
5 import dorm.model;
6 
7 import std.meta;
8 import std.traits;
9 
10 /**
11  * UDA to mark patch structs with, to make selecting them easier.
12  *
13  * Examples:
14  * ---
15  * @DormPatch!User
16  * struct UserSelection
17  * {
18  *     string username;
19  * }
20  * db.select!UserSelection;
21  *
22  * // is equivalent to
23  * struct UserSelection
24  * {
25  *     string username;
26  * }
27  * db.select!(User, UserSelection);
28  *
29  * // is equivalent to
30  * db.select!(User, "username");
31  *
32  * // is equivalent to
33  * db.select!(User, User.username);
34  *
35  * // is equivalent to
36  * db.select!(User, Tuple!(string, "username"));
37  * ---
38  */
39 struct DormPatch(User)
40 {
41 }
42 
43 /// Helper to validate if a DormPatch annotated field is valid for the given
44 /// Model type.
45 mixin template ValidatePatch(Patch, TModel)
46 {
47 	import std.traits : hasUDA;
48 	import dorm.annotations : isDormFieldAttribute;
49 
50 	static if (!isImplicitPatch!(TModel, Patch))
51 	{
52 		static assert (hasUDA!(Patch, DormPatch!TModel), "Patch struct " ~ Patch.stringof
53 			~ " must be annoated using DormPatch!(" ~ TModel.stringof ~ ") exactly once!");
54 
55 		static foreach (i, field; Patch.tupleof)
56 		{
57 			static assert (__traits(hasMember, TModel.init, Patch.tupleof[i].stringof),
58 				"\n" ~ SourceLocation(__traits(getLocation, Patch.tupleof[i])).toErrorString
59 					~ "Patch field `" ~ Patch.tupleof[i].stringof
60 					~ "` is not defined on DB Type " ~ TModel.stringof
61 					~ ".\n\tAvailable usable fields: "
62 						~ DormFields!TModel.map!(f => f.sourceColumn).join(", "));
63 
64 			static foreach (attr; __traits(getAttributes, Patch.tupleof[i]))
65 				static assert (!isDormFieldAttribute!attr,
66 					"\n" ~ SourceLocation(__traits(getLocation, Patch.tupleof[i])).toErrorString
67 						~ "Patch field `" ~ Patch.tupleof[i].stringof
68 						~ "` defines DB-related annotations, which is not"
69 						~ " supported. Put annotations on the Model field instead!");
70 		}
71 	}
72 }
73 
74 /// Checks if Patch is an implicit patch of TModel. That is a child struct of
75 /// the given model class.
76 enum isImplicitPatch(TModel, Patch) = is(__traits(parent, Patch) == TModel);
77 
78 template ImplicitPatchFieldName(TModel, Patch)
79 {
80 	static assert(isImplicitPatch!(TModel, Patch));
81 
82 	static foreach (member; TModel.tupleof)
83 	{
84 		static if (is(typeof(member) == Patch))
85 		{
86 			enum ImplicitPatchFieldName = __traits(identifier, member);
87 		}
88 	}
89 }
90 
91 /// Checks if the given type Patch is either an implicit or an explicit patch.
92 enum isSomePatch(Patch) =
93 	is(Patch == struct) && (is(__traits(parent, Patch) : Model)
94 		|| hasUDA!(Patch, DormPatch));
95 enum isSomePatch(alias other) = false;
96 
97 static template PatchFromIdOrPatch(alias idOrPatch)
98 {
99 	static if (is(idOrPatch : Model))
100 		alias PatchFromIdOrPatch = idOrPatch;
101 	else static if (isSomePatch!idOrPatch)
102 		alias PatchFromIdOrPatch = idOrPatch;
103 	else static if (is(__traits(parent, idOrPatch) : Model)
104 		|| isSomePatch!(__traits(parent, idOrPatch)))
105 		alias PatchFromIdOrPatch = __traits(parent, idOrPatch);
106 	else
107 		static assert(false, "Invalid id or patch: " ~ idOrPatch.stringof);
108 }
109 
110 static template IdAliasFromIdOrPatch(alias idOrPatch)
111 {
112 	static if (is(idOrPatch : Model))
113 		alias IdAliasFromIdOrPatch =
114 			__traits(getMember, idOrPatch, DormPrimaryKey!idOrPatch.sourceColumn);
115 	else static if (isSomePatch!idOrPatch)
116 		alias IdAliasFromIdOrPatch =
117 			__traits(getMember, idOrPatch,
118 				DormPrimaryKey!(ModelFromSomePatch!idOrPatch).sourceColumn);
119 	else static if (is(__traits(parent, idOrPatch) : Model)
120 		|| isSomePatch!(__traits(parent, idOrPatch)))
121 		alias IdAliasFromIdOrPatch = idOrPatch;
122 	else
123 		static assert(false, "Invalid id or patch: " ~ idOrPatch.stringof);
124 }
125 
126 static template ModelFromIdOrModel(alias idOrModel)
127 {
128 	static if (is(idOrModel : Model))
129 		alias ModelFromIdOrModel = idOrModel;
130 	else static if (is(__traits(parent, idOrModel) : Model))
131 		alias ModelFromIdOrModel = __traits(parent, idOrModel);
132 	else
133 		static assert(false, "Invalid id or model: " ~ idOrModel.stringof);
134 }
135 
136 static template IdAliasFromIdOrModel(alias idOrModel)
137 {
138 	static if (is(idOrModel : Model))
139 		alias IdAliasFromIdOrModel =
140 			__traits(getMember, idOrModel, DormPrimaryKey!idOrModel.sourceColumn);
141 	else static if (is(__traits(parent, idOrModel) : Model))
142 		alias IdAliasFromIdOrModel = idOrModel;
143 	else
144 		static assert(false, "Invalid id or model: " ~ idOrModel.stringof);
145 }
146 
147 static template ModelFromSomePatch(TModel)
148 {
149 	static if (is(__traits(parent, TModel) : Model))
150 		alias ModelFromSomePatch = __traits(parent, TModel);
151 	else
152 		alias ModelFromSomePatch = DBType!TModel;
153 }
154 
155 static template TypeOfField(TModel, alias field)
156 {
157 	static if (is(__traits(parent, field) == TModel)
158 		|| is(__traits(parent, __traits(parent, field)) == TModel))
159 		alias TypeOfField = typeof(field);
160 	else
161 		static assert(false,
162 			"Can't use field " ~ field.stringof ~ " on Model type " ~ TModel.stringof);
163 }
164 
165 template DBType(Selection...)
166 {
167 	static assert(Selection.length >= 1);
168 
169 	static if (Selection.length > 1)
170 	{
171 		alias DBType = GetModelDBType!(Selection[0]);
172 	}
173 	else
174 	{
175 		alias PatchAttrs = getUDAs!(Selection[0], DormPatch);
176 		static if (PatchAttrs.length == 0)
177 			alias DBType = GetModelDBType!(Selection[0]);
178 		else static if (PatchAttrs.length == 1)
179 		{
180 			static if (is(PatchAttrs[0] == DormPatch!T, T))
181 			{
182 				mixin ValidatePatch!(Selection[0], T);
183 				alias DBType = T;
184 			}
185 			else
186 				static assert(false, "internal template error");
187 		}
188 		else
189 			static assert(false, "Cannot annotate DormPatch struct with multiple DormPatch UDAs.");
190 	}
191 }
192 
193 template GetModelDBType(T)
194 {
195 	import dorm.model : Model;
196 
197 	static if (is(T : Model))
198 		alias GetModelDBType = T;
199 	else static if (is(__traits(parent, T) : Model))
200 		alias GetModelDBType = __traits(parent, T);
201 	else
202 		static assert(false, "Passed in Non-Model Type where a Model was expected");
203 }
204 
205 template SelectType(T, Selection...)
206 {
207 	import std.traits : isAggregateType;
208 
209 	static if (Selection.length == 0)
210 		alias SelectType = T;
211 	else static if (Selection.length == 1 && isAggregateType!(Selection[0]))
212 		alias SelectType = Selection[0];
213 	else
214 		alias SelectType = BuildFieldsTuple!(T, Selection);
215 }
216 
217 template BuildFieldsTuple(T, Selection...)
218 {
219 	import std.meta : AliasSeq;
220 	import std.typecons : Tuple;
221 
222 	alias TupleArgs = AliasSeq!();
223 	static foreach (alias Field; Selection)
224 	{
225 		static if (__traits(compiles, { string s = Field; }))
226 			alias TupleArgs = AliasSeq!(TupleArgs, typeof(__traits(getMember, T, Field)), Field);
227 		else
228 			alias TupleArgs = AliasSeq!(TupleArgs, typeof(Field), __traits(identifier, Field));
229 	}
230 	alias BuildFieldsTuple = Tuple!TupleArgs;
231 }
232 
233 template FilterLayoutFields(T, TSelect)
234 {
235 	static if (is(T == TSelect))
236 		enum FilterLayoutFields = DormFields!T;
237 	else static if (is(TSelect : Model))
238 		static assert(false, "Cannot filter for fields of Model class on a Model class");
239 	else static if (isImplicitPatch!(T, TSelect))
240 		enum FilterLayoutFields = filterFieldsPrefixed!T(
241 			ImplicitPatchFieldName!(T, TSelect) ~ ".",
242 			implicitPatchSelectionFieldNames!(T, TSelect)
243 		);
244 	else
245 		enum FilterLayoutFields = filterFields!T(selectionFieldNames!(T, TSelect));
246 }
247 
248 private auto filterFields(T)(string[] sourceNames...)
249 {
250 	import std.algorithm : canFind;
251 
252 	enum fields = DormFields!T;
253 	typeof(fields) ret;
254 	foreach (ref field; fields)
255 		if (sourceNames.canFind(field.sourceColumn))
256 			ret ~= field;
257 	return ret;
258 }
259 
260 private auto filterFieldsPrefixed(T)(string prefix, string[] sourceNames...)
261 {
262 	import std.algorithm : canFind, startsWith;
263 
264 	enum fields = DormFields!T;
265 	typeof(fields) ret;
266 	foreach (ref field; fields)
267 		if (field.sourceColumn.startsWith(prefix)
268 			&& sourceNames.canFind(field.sourceColumn[prefix.length .. $]))
269 		{
270 			field.sourceColumn = field.sourceColumn[prefix.length .. $];
271 			ret ~= field;
272 		}
273 	return ret;
274 }
275 
276 private string[] implicitPatchSelectionFieldNames(T, TSelect)()
277 {
278 	static foreach (field; __traits(allMembers, T))
279 		static if (field == ImplicitPatchFieldName!(T, TSelect))
280 			return selectionFieldNames!(T, TSelect)();
281 }
282 
283 private string[] selectionFieldNames(T, TSelect)(string prefix = "")
284 {
285 	import std.algorithm : canFind;
286 
287 	enum layout = DormLayout!T;
288 
289 	string[] ret;
290 	static foreach (field; __traits(allMembers, TSelect))
291 	{
292 		static if (layout.embeddedStructs.canFind(field))
293 			ret ~= selectionFieldNames!(T, typeof(__traits(getMember, TSelect, field)))(
294 				prefix ~ field ~ ".");
295 		else
296 			ret ~= (prefix ~ field);
297 	}
298 	return ret;
299 }