1 ///
2 module dpq.attributes;
3 
4 import std.traits;
5 import std.typetuple;
6 import std.typecons;
7 
8 import dpq.column;
9 import dpq.meta;
10 
11 version(unittest) import std.stdio;
12 
13 /** 
14 	@relation attribute -- specifies the relation/type name to be used
15 	for Connection's ORM functions. If none is set, dpq will
16 	default to the structure's name in lower_snake_case.
17  */
18 RelationAttribute relation(string name)
19 {
20 	return RelationAttribute(name);
21 }
22 
23 package struct RelationAttribute
24 {
25 	string name;
26 }
27 
28 enum EmbedAttribute;
29 deprecated("There is no need to use @embed anymore, Serialisers should take care of it")
30 	alias embed = EmbedAttribute;
31 
32 /**
33 	@attribute / @attr -- specifies the name of the attribute to be used.
34 
35 	Defaults to member's name in lower_snake_case if nothing is specified
36  */
37 AttributeAttribute attribute(string name)
38 {
39 	return AttributeAttribute(name);
40 }
41 alias attr = attribute;
42 
43 package struct AttributeAttribute
44 {
45 	string name;
46 }
47 
48 /**
49 	@PK / @Pkey / @PrimaryKey attribute -- specifies that the member should be
50 	a PK. Coumpound PKs are not supported.
51  */
52 package enum PrimaryKeyAttribute;
53 
54 alias PrimaryKey = PrimaryKeyAttribute;
55 alias PKey = PrimaryKey;
56 alias PK = PrimaryKey;
57 
58 
59 
60 /**
61 	Specifies the type for the member, overrides any serialiser-provided type
62  */
63 PGTypeAttribute type(string type)
64 {
65 	return PGTypeAttribute(type);
66 }
67 
68 /// Shortcut for @type("SERIAL")
69 @property PGTypeAttribute serial()
70 {
71 	return PGTypeAttribute("SERIAL");
72 }
73 
74 /// SHORTCUT for @type("SERIAL4")
75 @property PGTypeAttribute serial4()
76 {
77 	return PGTypeAttribute("SERIAL4");
78 }
79 
80 /// SHORTCUT for @type("SERIAL8")
81 @property PGTypeAttribute serial8()
82 {
83 	return PGTypeAttribute("SERIAL8");
84 }
85 
86 package struct PGTypeAttribute
87 {
88 	string type;
89 }
90 
91 /**
92 	Allows placing any text after the column definition when ensureSchema is ran.
93 
94 	Useful for stuff like "NOT NULL", or "CHECK (x > y)", ...
95 
96 	Do not use to create foreign keys, use @FK instead.
97 
98 	Example:
99 	-------------
100 	@relation("testy")
101 	struct Test
102 	{
103 		@serial @PK int id;
104 		@suffix("NOT NULL") string username; // will produce "username TEXT NOT NULL"
105 	}
106 	-------------
107  */
108 ColumnSuffixAttribute suffix(string suffix)
109 {
110 	return ColumnSuffixAttribute(suffix);
111 }
112 
113 struct ColumnSuffixAttribute
114 {
115 	string suffix;
116 }
117 
118 /**
119 	A shortcut to @suffix("NOT NULL")
120 
121 	Does not perform any kind of a null check in D.
122  */
123 enum notNull = suffix("NOT NULL");
124 
125 /**
126 	Specifies that the member should be completely ignored as far as the DB is
127 	concerned.
128  */
129 package enum IgnoreAttribute;
130 alias ignore = IgnoreAttribute;
131 
132 /**
133 	Specifies that the member/column should have an index created on it.
134 	If unique is set, it well be a unique index. 
135  */
136 package struct IndexAttribute
137 {
138 	bool unique = false;
139 }
140 
141 /// @index
142 @property IndexAttribute index()
143 {
144 	return IndexAttribute();
145 }
146 
147 /// @uniqueIndex
148 @property IndexAttribute uniqueIndex()
149 {
150 	return IndexAttribute(true);
151 }
152 
153 /**
154 	Specifies that the member is a foriegn key, ensureSchema will create a FK 
155 	constraint as well as an index for it. Finds the referenced table's PK
156 	by itself.
157  */
158 package struct ForeignKeyAttribute
159 {
160 	string relation;
161 	string pkey;
162 }
163 
164 @property ForeignKeyAttribute foreignKey(T)()
165 {
166 	return ForeignKeyAttribute(relationName!T, primaryKeyName!T);
167 }
168 alias FK = foreignKey;
169 alias FKey = foreignKey;
170 
171 /**
172 	Transforms the given string into lower_snake_case at compile-time.
173 	Used for attribute and relation names and probably not very useful
174 	outside the library itself.
175  */
176 template SnakeCase(string str)
177 {
178 	import std..string : toLower;
179 
180 	template IsLower(char c)
181 	{
182 		enum IsLower = (c >= 'a' && c <= 'z');
183 	}
184 	template IsUpper(char c)
185 	{
186 		enum IsUpper = (c >= 'A' && c <= 'Z');
187 	}
188 
189 	// Ssss, sss.
190 	template Snake(string str)
191 	{
192 		static if (str.length < 2)
193 			enum Snake = str;
194 		else static if (IsLower!(str[0]) && IsUpper!(str[1]))
195 			enum Snake = str[0] ~ "_" ~ str[1] ~ SnakeCase!(str[2 .. $]);
196 		else
197 			enum Snake = str[0] ~ SnakeCase!(str[1 .. $]);
198 	}
199 
200 	enum SnakeCase = Snake!str.toLower;
201 }
202 
203 unittest
204 {
205 	writeln(" * Attributes");
206 	writeln("\t * SnakeCase");
207 
208 	static assert(SnakeCase!"something" == "something");
209 	static assert(SnakeCase!"UPPERCASE" == "uppercase");
210 	static assert(SnakeCase!"camelCase" == "camel_case");
211 	static assert(SnakeCase!"UpperCamelCase" == "upper_camel_case");
212 	static assert(SnakeCase!"someTHING" == "some_thing");
213 	static assert(SnakeCase!"some_thing" == "some_thing");
214 }
215 
216 /**
217 	Relation/type name for the given type, can be set with @relation attribute.
218 	If @relation is not set, type's name will be lower_snake_cased.
219  */
220 template relationName(alias R)
221 {
222 	static if (hasUDA!(R, RelationAttribute))
223 	{
224 		enum rName = getUDAs!(R, RelationAttribute)[0].name;
225 		static if (rName.length == 0)
226 			enum relationName = SnakeCase!(R.stringof);
227 		else
228 			enum relationName = rName;
229 	}
230 	else
231 		enum relationName = SnakeCase!(R.stringof);
232 }
233 
234 unittest
235 {
236 	writeln("\t * relationName");
237 
238 	struct Test {}
239 	struct someThing {}
240 	@relation("some_random_name") struct Test2 {}
241 
242 	static assert(relationName!Test == "test");
243 	static assert(relationName!someThing == "some_thing");
244 	static assert(relationName!Test2 == "some_random_name");
245 }
246 
247 /**
248 	Attribute name for the given type, can be specified with @attribute.
249 	If @attribute is not specified, the member's name will just be 
250 	lower_snake_cased and returned.
251  */
252 template attributeName(alias R)
253 {
254 	static if (hasUDA!(R, AttributeAttribute))
255 		enum attributeName = getUDAs!(R, AttributeAttribute)[0].name;
256 	else
257 		enum attributeName = SnakeCase!(__traits(identifier, R));
258 }
259 
260 unittest
261 {
262 	writeln("\t * attributeName");
263 	struct Test
264 	{
265 		string id;
266 		string someName;
267 		@attr("someRandomName") string a;
268 	}
269 
270 	static assert(attributeName!(Test.id) == "id");
271 	static assert(attributeName!(Test.someName) == "some_name");
272 	static assert(attributeName!(Test.a) == "someRandomName");
273 }
274 
275 
276 /**
277  Workaround for getSymbolsByUDA not working on structs/classes with private members.
278  Returns all the structure's members that have the given UDA.
279  */
280 template getMembersByUDA(T, alias attribute)
281 {
282 	import std.meta : Filter;
283 
284 	enum hasSpecificUDA(string name) = mixin("hasUDA!(T." ~ name ~ ", attribute)");
285 	alias getMembersByUDA = Filter!(hasSpecificUDA, __traits(allMembers, T));
286 }
287 
288 unittest
289 {
290 	writeln("\t * getMembersByUDA");
291 
292 	struct Test
293 	{
294 		@PK int id;
295 		@FK!Test int id2;
296 		@FK!Test int id3;
297 	}
298 
299 	alias FKMembers = getMembersByUDA!(Test, ForeignKeyAttribute);
300 	alias PKMembers = getMembersByUDA!(Test, PrimaryKeyAttribute);
301 
302 	static assert(PKMembers.length == 1);
303 	static assert(PKMembers[0] == "id");
304 
305 	static assert(FKMembers.length == 2);
306 	static assert(FKMembers[0] == "id2");
307 	static assert(FKMembers[1] == "id3");
308 
309 	static assert(getMembersByUDA!(Test, IgnoreAttribute).length == 0);
310 }
311 
312 /**
313 	Returns a string containing the name of the type member that is marked with @PK
314  */
315 template primaryKeyName(T)
316 {
317 	alias fields = getMembersByUDA!(T, PrimaryKeyAttribute);
318 	static assert(fields.length < 2,
319 			"Multiple or composite primary key found for " ~ T.stringof ~ ", this is not currently supported");
320 	static assert(fields.length == 1, "No primary key found for " ~ T.stringof);
321 
322 	enum primaryKeyName = mixin(fields[0].stringof);
323 }
324 
325 /**
326 	Returns the name of the PK attribute (SQL name)
327  */
328 template primaryKeyAttributeName(T)
329 {
330 	enum primaryKeyAttributeName = attributeName!(mixin("T." ~ primaryKeyName!T));
331 }
332 
333 unittest
334 {
335 	writeln("\t * primaryKeyName");
336 	struct Test
337 	{
338 		@PK int myPK;
339 		int id;
340 	}
341 
342 	static assert(primaryKeyName!Test == "myPK");
343 
344 	writeln("\t * primaryKeyAttributeName");
345 
346 	static assert(primaryKeyAttributeName!Test == "my_pk");
347 }
348 
349 /**
350 	Returns true if the member m is a PK on T.
351  */
352 template isPK(alias T, string m)
353 {
354 	enum isPK = hasUDA!(mixin("T." ~ m), PrimaryKeyAttribute);
355 }
356 
357 unittest
358 {
359 	writeln("\t * isPK");
360 	struct Test
361 	{
362 		@PK int id;
363 		string a;
364 	}
365 	
366 	static assert(isPK!(Test, "id"));
367 	static assert(!isPK!(Test, "a"));
368 }
369 
370 deprecated template embeddedPrefix(T, string name)
371 {
372 	import std..string : format;
373 	enum embeddedPrefix ="_%s_%s_".format(
374 			relationName!T,
375 			SnakeCase!name);
376 }
377 
378 /**
379 	Returns a list of Columns for all the given type's serialisable members,
380 	with their actual names as they're used in SQL.
381 
382 	Params:
383 		prefix   = prefix to use, if any
384 		asPrefix = asPrefix, prefix to use for column's AS names
385 		ignorePK = whether to ignore the PK, useful for inserts and similar
386  */
387 template AttributeList2(
388 		T,
389 		string prefix = "",
390 		string asPrefix = "",
391 		bool ignorePK = false,
392 		fields...)
393 {
394 	static if (fields.length == 0)
395 		enum AttributeList2 = [];
396 	else
397 	{
398 		alias mt = typeof(mixin("T." ~ fields[0]));
399 
400 		// Ignore the PK
401 		static if (ignorePK && isPK!(T, fields[0]) || hasUDA!(IgnoreAttribute, mixin("T." ~ fields[0])))
402 			enum AttributeList2 = AttributeList2!(T, prefix, asPrefix, ignorePK, fields[1 .. $]);
403 		else
404 		{
405 			enum attrName = attributeName!(mixin("T." ~ fields[0]));
406 			enum AttributeList2 = 
407 					[Column(prefix ~ attrName, asPrefix ~ attrName)] ~ 
408 					AttributeList2!(
409 							T,
410 							prefix,
411 							asPrefix,
412 							ignorePK,
413 							fields[1 .. $]);
414 		}
415 	}
416 }
417 
418 template AttributeList(T, bool ignorePK = false, bool insert = false)
419 {
420 	alias AttributeList = AttributeList2!(T, "", "", ignorePK, serialisableMembers!(T));
421 	static assert(AttributeList.length > 0, "AttributeList found no fields, for " ~ T.stringof ~ " cannot continue");
422 }
423 
424 unittest
425 {
426 	import std.typecons : Nullable;
427 	writeln("\t * AttributeList");
428 	struct Test2
429 	{
430 		string bar;
431 		string baz;
432 	}
433 
434 	struct Test
435 	{
436 		@PK int id;
437 		Test2 inner;
438 	}
439 
440 	alias attrs = AttributeList!Test;
441 	static assert(attrs[0] == Column("id", "id"));
442 	static assert(attrs[1] == Column("inner", "inner"));
443 
444 	// ignorePK
445 	static assert(AttributeList!(Test, true)[0] == Column("inner", "inner"));
446 
447 	// INSERT syntax, with ignorePK
448 	static assert(AttributeList!(Test, true)[0] == Column("inner", "inner"));
449 }
450 
451 /**
452 	Gives a list of all the structure's members that will be used in the DB.
453 	Ignores @ignore members, non-RW and non-public members.
454  */
455 template serialisableMembers(T)
456 {
457 	alias RT = RealType!T;
458 	alias serialisableMembers = filterSerialisableMembers!(RT, __traits(allMembers, RT));
459 }
460 
461 unittest
462 {
463 	writeln("\t * serialisableMembers");
464 	struct Test
465 	{
466 		int a;
467 		private int _b;
468 		@property int b() {return _b;};
469 		@property void b(int x) {_b = x;};
470 		@property int c() {return _b;};
471 	}
472 
473 	static assert(serialisableMembers!Test.length == 2);
474 	static assert(serialisableMembers!Test[0] == "a");
475 	static assert(serialisableMembers!Test[1] == "b");
476 }
477 
478 /// A filter implementation for serialisableMembers
479 template filterSerialisableMembers(T, fields...)
480 {
481 	static if (fields.length == 0)
482 		alias filterSerialisableMembers = TypeTuple!();
483 	else
484 	{
485 		enum m = fields[0];
486 		static if (isRWPlainField!(T, m) || isRWField!(T, m))
487 		{
488 			static if (!hasUDA!(mixin("T." ~ m), IgnoreAttribute))
489 				alias filterSerialisableMembers = TypeTuple!(
490 						TypeTuple!(m),
491 						filterSerialisableMembers!(T, fields[1 .. $]));
492 			else
493 				alias filterSerialisableMembers = filterSerialisableMembers!(T, fields[1 .. $]);
494 		}
495 		else 
496 			alias filterSerialisableMembers = filterSerialisableMembers!(T, fields[1 .. $]);
497 	} 
498 }
499 
500 
501 /*
502 	Functions/templates isRWPlainField, isRWField, isPublicMember and isNonStaticMember.
503 
504 	Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos,
505 	some are dirty hacks that work only for vibe.d
506 	Copyright: © 2012 RejectedSoftware e.K.
507 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
508 	Authors: Sönke Ludwig, Михаил Страшун
509 */
510 
511 
512 /**
513 	Determins if a member is a public, non-static data field.
514 */
515 
516 template isRWPlainField(T, string M)
517 {
518 	static if (!isRWField!(T, M)) 
519 		enum isRWPlainField = false;
520 	else 
521 		enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
522 }
523 
524 /**
525 	Determines if a member is a public, non-static, de-facto data field.
526 	In addition to plain data fields, R/W properties are also accepted.
527 */
528 template isRWField(T, string M)
529 {
530 	import std.traits;
531 	import std.typetuple;
532 
533 	static void testAssign()() 
534 	{
535 		T t = void;
536 		__traits(getMember, t, M) = __traits(getMember, t, M);
537 	}
538 
539 	// reject type aliases
540 	static if (is(TypeTuple!(__traits(getMember, T, M))))
541 		enum isRWField = false;
542 	// reject non-public members
543 	else static if (!isPublicMember!(T, M))
544 		enum isRWField = false;
545 	// reject static members
546 	else static if (!isNonStaticMember!(T, M))
547 		enum isRWField = false;
548 	// reject non-typed members
549 	else static if (!is(typeof(__traits(getMember, T, M))))
550 		enum isRWField = false;
551 	// reject void typed members (includes templates)
552 	else static if (is(typeof(__traits(getMember, T, M)) == void))
553 		enum isRWField = false;
554 	// reject non-assignable members
555 	else static if (!__traits(compiles, testAssign!()()))
556 		enum isRWField = false;
557 	else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M)))
558 	{
559 		// If M is a function, reject if not @property or returns by ref
560 		private enum FA = functionAttributes!(__traits(getMember, T, M));
561 		enum isRWField = (FA & FunctionAttribute.property) != 0;
562 	}
563 	else
564 	{
565 		enum isRWField = true;
566 	}
567 }
568 
569 template isPublicMember(T, string M)
570 {
571 	import std.algorithm, std.typetuple : TypeTuple;
572 
573 	static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) 
574 		enum isPublicMember = false;
575 	else 
576 	{
577 		alias MEM = TypeTuple!(__traits(getMember, T, M));
578 		enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
579 	}
580 }
581 
582 template isNonStaticMember(T, string M)
583 {
584 	import std.typetuple;
585 	import std.traits;
586 	
587 	alias MF = TypeTuple!(__traits(getMember, T, M));
588 
589 	static if (M.length == 0)
590 	{
591 	    enum isNonStaticMember = false;
592 	}
593 	else static if (anySatisfy!(isSomeFunction, MF))
594 	{
595 	    enum isNonStaticMember = !__traits(isStaticFunction, MF);
596 	}
597 	else
598 	{
599 		enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
600 	}
601 }