1 module dpq.attributes;
2 
3 import std.traits;
4 import std.typetuple;
5 import std.typecons;
6 
7 import dpq.column;
8 import dpq.meta;
9 
10 version(unittest)
11 {
12 	import std.stdio;
13 }
14 
15 RelationAttribute relation(string name)
16 {
17 	return RelationAttribute(name);
18 }
19 
20 struct RelationAttribute
21 {
22 	string name;
23 }
24 
25 enum EmbedAttribute;
26 alias embed = EmbedAttribute;
27 
28 AttributeAttribute attribute(string name)
29 {
30 	return AttributeAttribute(name);
31 }
32 alias attr = attribute;
33 
34 struct AttributeAttribute
35 {
36 	string name;
37 }
38 
39 @property PrimaryKeyAttribute PrimaryKey()
40 {
41 	return PrimaryKeyAttribute();
42 }
43 
44 alias PKey = PrimaryKey;
45 alias PK = PrimaryKey;
46 
47 struct PrimaryKeyAttribute
48 {
49 }
50 
51 
52 PGTypeAttribute type(string type)
53 {
54 	return PGTypeAttribute(type);
55 }
56 
57 @property PGTypeAttribute serial()
58 {
59 	return PGTypeAttribute("SERIAL");
60 }
61 
62 @property PGTypeAttribute serial4()
63 {
64 	return PGTypeAttribute("SERIAL4");
65 }
66 
67 @property PGTypeAttribute serial8()
68 {
69 	return PGTypeAttribute("SERIAL8");
70 }
71 
72 
73 struct PGTypeAttribute
74 {
75 	string type;
76 }
77 
78 enum IgnoreAttribute;
79 alias ignore = IgnoreAttribute;
80 
81 struct IndexAttribute
82 {
83 	bool unique = false;
84 }
85 
86 @property IndexAttribute index()
87 {
88 	return IndexAttribute();
89 }
90 
91 @property IndexAttribute uniqueIndex()
92 {
93 	return IndexAttribute(true);
94 }
95 
96 struct ForeignKeyAttribute
97 {
98 	string relation;
99 	string pkey;
100 }
101 
102 @property ForeignKeyAttribute foreignKey(T)()
103 {
104 	return ForeignKeyAttribute(relationName!T, primaryKeyName!T);
105 }
106 alias FK = foreignKey;
107 alias FKey = foreignKey;
108 
109 template SnakeCase(string str)
110 {
111 	import std..string : toLower;
112 
113 	template IsLower(char c)
114 	{
115 		enum IsLower = (c >= 'a' && c <= 'z');
116 	}
117 	template IsUpper(char c)
118 	{
119 		enum IsUpper = (c >= 'A' && c <= 'Z');
120 	}
121 
122 	// Ssss, sss.
123 	template Snake(string str)
124 	{
125 		static if (str.length < 2)
126 			enum Snake = str;
127 		else static if (IsLower!(str[0]) && IsUpper!(str[1]))
128 			enum Snake = str[0] ~ "_" ~ str[1] ~ SnakeCase!(str[2 .. $]);
129 		else
130 			enum Snake = str[0] ~ SnakeCase!(str[1 .. $]);
131 	}
132 
133 	enum SnakeCase = Snake!str.toLower;
134 }
135 
136 unittest
137 {
138 	writeln(" * Attributes");
139 	writeln("\t * SnakeCase");
140 
141 	static assert(SnakeCase!"something" == "something");
142 	static assert(SnakeCase!"UPPERCASE" == "uppercase");
143 	static assert(SnakeCase!"camelCase" == "camel_case");
144 	static assert(SnakeCase!"UpperCamelCase" == "upper_camel_case");
145 	static assert(SnakeCase!"someTHING" == "some_thing");
146 	static assert(SnakeCase!"some_thing" == "some_thing");
147 }
148 
149 template relationName(alias R)
150 {
151 	static if (hasUDA!(R, RelationAttribute))
152 	{
153 		enum rName = getUDAs!(R, RelationAttribute)[0].name;
154 		static if (rName.length == 0)
155 			enum relationName = SnakeCase!(R.stringof);
156 		else
157 			enum relationName = rName;
158 	}
159 	else
160 		enum relationName = SnakeCase!(R.stringof);
161 }
162 
163 unittest
164 {
165 	writeln("\t * relationName");
166 
167 	struct Test {}
168 	struct someThing {}
169 	@relation("some_random_name") struct Test2 {}
170 
171 	static assert(relationName!Test == "test");
172 	static assert(relationName!someThing == "some_thing");
173 	static assert(relationName!Test2 == "some_random_name");
174 }
175 
176 
177 template attributeName(alias R)
178 {
179 	static if (hasUDA!(R, AttributeAttribute))
180 		enum attributeName = getUDAs!(R, AttributeAttribute)[0].name;
181 	else
182 		enum attributeName = SnakeCase!(__traits(identifier, R));
183 }
184 
185 unittest
186 {
187 	writeln("\t * attributeName");
188 	struct Test
189 	{
190 		string id;
191 		string someName;
192 		@attr("someRandomName") string a;
193 	}
194 
195 	static assert(attributeName!(Test.id) == "id");
196 	static assert(attributeName!(Test.someName) == "some_name");
197 	static assert(attributeName!(Test.a) == "someRandomName");
198 }
199 
200 
201 // Workaround for getSymbolsByUDA not working on structs/classes with private members
202 template getMembersByUDA(T, alias attribute)
203 {
204 	import std.meta : Filter;
205 
206 	enum hasSpecificUDA(string name) = mixin("hasUDA!(T." ~ name ~ ", attribute)");
207 	alias getMembersByUDA = Filter!(hasSpecificUDA, __traits(allMembers, T));
208 }
209 
210 unittest
211 {
212 	writeln("\t * getMembersByUDA");
213 
214 	struct Test
215 	{
216 		@PK int id;
217 		@FK!Test int id2;
218 		@FK!Test int id3;
219 	}
220 
221 	static assert(getMembersByUDA!(Test, PrimaryKeyAttribute)[0] == "id");
222 	static assert(getMembersByUDA!(Test, ForeignKeyAttribute)[0] == "id2");
223 	static assert(getMembersByUDA!(Test, ForeignKeyAttribute)[1] == "id3");
224 }
225 
226 template primaryKeyName(T)
227 {
228 	alias fields = getMembersByUDA!(T, PrimaryKeyAttribute);
229 	static assert(fields.length < 2,
230 			"Multiple or composite primary key found for " ~ T.stringof ~ ", this is not currently supported");
231 	static assert(fields.length == 1, "No primary key found for " ~ T.stringof);
232 
233 	enum primaryKeyName = mixin(fields[0].stringof);
234 }
235 
236 template primaryKeyAttributeName(T)
237 {
238 	enum primaryKeyAttributeName = attributeName!(mixin("T." ~ primaryKeyName!T));
239 }
240 
241 unittest
242 {
243 	writeln("\t * primaryKeyName");
244 	struct Test
245 	{
246 		@PK int myPK;
247 		int id;
248 	}
249 
250 	static assert(primaryKeyName!Test == "myPK");
251 
252 	writeln("\t * primaryKeyAttributeName");
253 
254 	static assert(primaryKeyAttributeName!Test == "my_pk");
255 }
256 
257 template isPK(alias T, string m)
258 {
259 	enum isPK = hasUDA!(mixin("T." ~ m), PrimaryKeyAttribute);
260 }
261 
262 unittest
263 {
264 	writeln("\t * isPK");
265 	struct Test
266 	{
267 		@PK int id;
268 		string a;
269 	}
270 	
271 	static assert(isPK!(Test, "id"));
272 	static assert(!isPK!(Test, "a"));
273 }
274 
275 template embeddedPrefix(T, string name)
276 {
277 	import std..string : format;
278 	enum embeddedPrefix ="_%s_%s_".format(
279 			relationName!T,
280 			SnakeCase!name);
281 }
282 
283 template AttributeList2(
284 		T,
285 		string prefix = "",
286 		string asPrefix = "",
287 		bool ignorePK = false,
288 		bool insert = false,
289 		fields...)
290 {
291 	static if (fields.length == 0)
292 		enum AttributeList2 = [];
293 	else
294 	{
295 		alias mt = typeof(mixin("T." ~ fields[0]));
296 
297 		// Ignore the PK
298 		static if (ignorePK && isPK!(T, fields[0]) || hasUDA!(IgnoreAttribute, mixin("T." ~ fields[0])))
299 			enum AttributeList2 = AttributeList2!(T, prefix, asPrefix, ignorePK, insert, fields[1 .. $]);
300 		else
301 		{
302 			enum attrName = attributeName!(mixin("T." ~ fields[0]));
303 			enum AttributeList2 = 
304 					[Column(prefix ~ attrName, asPrefix ~ attrName)] ~ 
305 					AttributeList2!(
306 							T,
307 							prefix,
308 							asPrefix,
309 							ignorePK,
310 							insert,
311 							fields[1 .. $]);
312 		}
313 	}
314 }
315 
316 unittest
317 {
318 	import std.typecons : Nullable;
319 	writeln("\t * AttributeList");
320 	struct Test2
321 	{
322 		string bar;
323 		string baz;
324 	}
325 
326 	struct Test
327 	{
328 		@PK int id;
329 		@embed Test2 inner;
330 	}
331 
332 	static assert(AttributeList!Test[0] == Column("id", "id"));
333 	static assert(AttributeList!Test[1] == Column("inner", "inner"));
334 
335 	// ignorePK
336 	static assert(AttributeList!(Test, true)[0] == Column("inner", "inner"));
337 
338 	// INSERT syntax, with ignorePK
339 	static assert(AttributeList!(Test, true, true)[0] == Column("inner", "inner"));
340 
341 
342 }
343 
344 template AttributeList(T, bool ignorePK = false, bool insert = false)
345 {
346 	alias AttributeList = AttributeList2!(T, "", "", ignorePK, insert, serialisableMembers!(T));
347 	static assert(AttributeList.length > 0, "AttributeList found no fields, for " ~ T.stringof ~ " cannot continue");
348 }
349 
350 template serialisableMembers(T)
351 {
352 	alias NT = NoNullable!T;
353 	alias serialisableMembers = filterSerialisableMembers!(NT, __traits(allMembers, NT));
354 }
355 
356 unittest
357 {
358 	writeln("\t * serialisableMembers");
359 	struct Test
360 	{
361 		int a;
362 		private int _b;
363 		@property int b() {return _b;};
364 		@property void b(int x) {_b = x;};
365 		@property int c() {return _b;};
366 	}
367 
368 	static assert(serialisableMembers!Test.length == 2);
369 	static assert(serialisableMembers!Test[0] == "a");
370 	static assert(serialisableMembers!Test[1] == "b");
371 }
372 
373 template filterSerialisableMembers(T, fields...)
374 {
375 	static if (fields.length == 0)
376 		alias filterSerialisableMembers = TypeTuple!();
377 	else
378 	{
379 		enum m = fields[0];
380 		static if (isRWPlainField!(T, m) || isRWField!(T, m))
381 		{
382 			static if (!hasUDA!(mixin("T." ~ m), IgnoreAttribute))
383 				alias filterSerialisableMembers = TypeTuple!(TypeTuple!(m), filterSerialisableMembers!(T, fields[1 .. $]));
384 			else
385 				alias filterSerialisableMembers = filterSerialisableMembers!(T, fields[1 .. $]);
386 		}
387 		else 
388 			alias filterSerialisableMembers = filterSerialisableMembers!(T, fields[1 .. $]);
389 	} 
390 }
391 
392 
393 /*
394 	 Functions below
395 
396 	Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos,
397 	some are dirty hacks that work only for vibe.d
398 	Copyright: © 2012 RejectedSoftware e.K.
399 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
400 	Authors: Sönke Ludwig, Михаил Страшун
401 */
402 
403 
404 /**
405 	Determins if a member is a public, non-static data field.
406 */
407 
408 template isRWPlainField(T, string M)
409 {
410 	static if (!isRWField!(T, M)) 
411 		enum isRWPlainField = false;
412 	else 
413 		enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
414 }
415 
416 /**
417 	Determines if a member is a public, non-static, de-facto data field.
418 	In addition to plain data fields, R/W properties are also accepted.
419 */
420 template isRWField(T, string M)
421 {
422 	import std.traits;
423 	import std.typetuple;
424 
425 	static void testAssign()() 
426 	{
427 		T t = void;
428 		__traits(getMember, t, M) = __traits(getMember, t, M);
429 	}
430 
431 	// reject type aliases
432 	static if (is(TypeTuple!(__traits(getMember, T, M))))
433 		enum isRWField = false;
434 	// reject non-public members
435 	else static if (!isPublicMember!(T, M))
436 		enum isRWField = false;
437 	// reject static members
438 	else static if (!isNonStaticMember!(T, M))
439 		enum isRWField = false;
440 	// reject non-typed members
441 	else static if (!is(typeof(__traits(getMember, T, M))))
442 		enum isRWField = false;
443 	// reject void typed members (includes templates)
444 	else static if (is(typeof(__traits(getMember, T, M)) == void))
445 		enum isRWField = false;
446 	// reject non-assignable members
447 	else static if (!__traits(compiles, testAssign!()()))
448 		enum isRWField = false;
449 	else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M)))
450 	{
451 		// If M is a function, reject if not @property or returns by ref
452 		private enum FA = functionAttributes!(__traits(getMember, T, M));
453 		enum isRWField = (FA & FunctionAttribute.property) != 0;
454 	}
455 	else
456 	{
457 		enum isRWField = true;
458 	}
459 }
460 
461 template isPublicMember(T, string M)
462 {
463 	import std.algorithm, std.typetuple : TypeTuple;
464 
465 	static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) 
466 		enum isPublicMember = false;
467 	else 
468 	{
469 		alias MEM = TypeTuple!(__traits(getMember, T, M));
470 		enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
471 	}
472 }
473 
474 template isNonStaticMember(T, string M)
475 {
476 	import std.typetuple;
477 	import std.traits;
478 
479 	alias MF = TypeTuple!(__traits(getMember, T, M));
480 	static if (M.length == 0) {
481 		enum isNonStaticMember = false;
482 	} else static if (anySatisfy!(isSomeFunction, MF)) {
483 		enum isNonStaticMember = !__traits(isStaticFunction, MF);
484 	} else {
485 		enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
486 	}
487 }