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 struct IgnoreAttribute
79 {
80 }
81 
82 @property IgnoreAttribute ignore()
83 {
84 	return IgnoreAttribute();
85 }
86 
87 struct IndexAttribute
88 {
89 	bool unique = false;
90 }
91 
92 @property IndexAttribute index()
93 {
94 	return IndexAttribute();
95 }
96 
97 @property IndexAttribute uniqueIndex()
98 {
99 	return IndexAttribute(true);
100 }
101 
102 struct ForeignKeyAttribute
103 {
104 	string relation;
105 	string pkey;
106 }
107 
108 @property ForeignKeyAttribute foreignKey(T)()
109 {
110 	return ForeignKeyAttribute(relationName!T, primaryKeyName!T);
111 }
112 alias FK = foreignKey;
113 alias FKey = foreignKey;
114 
115 template SnakeCase(string str)
116 {
117 	import std.string : toLower;
118 
119 	template IsLower(char c)
120 	{
121 		enum IsLower = (c >= 'a' && c <= 'z');
122 	}
123 	template IsUpper(char c)
124 	{
125 		enum IsUpper = (c >= 'A' && c <= 'Z');
126 	}
127 
128 	// Ssss, sss.
129 	template Snake(string str)
130 	{
131 		static if (str.length < 2)
132 			enum Snake = str;
133 		else static if (IsLower!(str[0]) && IsUpper!(str[1]))
134 			enum Snake = str[0] ~ "_" ~ str[1] ~ SnakeCase!(str[2 .. $]);
135 		else
136 			enum Snake = str[0] ~ SnakeCase!(str[1 .. $]);
137 	}
138 
139 	enum SnakeCase = Snake!str.toLower;
140 }
141 
142 unittest
143 {
144 	writeln(" * Attributes");
145 	writeln("\t * SnakeCase");
146 
147 	static assert(SnakeCase!"something" == "something");
148 	static assert(SnakeCase!"UPPERCASE" == "uppercase");
149 	static assert(SnakeCase!"camelCase" == "camel_case");
150 	static assert(SnakeCase!"UpperCamelCase" == "upper_camel_case");
151 	static assert(SnakeCase!"someTHING" == "some_thing");
152 	static assert(SnakeCase!"some_thing" == "some_thing");
153 }
154 
155 template relationName(alias R)
156 {
157 	static if (hasUDA!(R, RelationAttribute))
158 	{
159 		enum rName = getUDAs!(R, RelationAttribute)[0].name;
160 		static if (rName.length == 0)
161 			enum relationName = SnakeCase!(R.stringof);
162 		else
163 			enum relationName = rName;
164 	}
165 	else
166 		enum relationName = SnakeCase!(R.stringof);
167 }
168 
169 unittest
170 {
171 	writeln("\t * relationName");
172 
173 	struct Test {}
174 	struct someThing {}
175 	@relation("some_random_name") struct Test2 {}
176 
177 	static assert(relationName!Test == "test");
178 	static assert(relationName!someThing == "some_thing");
179 	static assert(relationName!Test2 == "some_random_name");
180 }
181 
182 
183 template attributeName(alias R)
184 {
185 	static if (hasUDA!(R, AttributeAttribute))
186 		enum attributeName = getUDAs!(R, AttributeAttribute)[0].name;
187 	else
188 		enum attributeName = SnakeCase!(__traits(identifier, R));
189 }
190 
191 unittest
192 {
193 	writeln("\t * attributeName");
194 	struct Test
195 	{
196 		string id;
197 		string someName;
198 		@attr("someRandomName") string a;
199 	}
200 
201 	static assert(attributeName!(Test.id) == "id");
202 	static assert(attributeName!(Test.someName) == "some_name");
203 	static assert(attributeName!(Test.a) == "someRandomName");
204 }
205 
206 
207 // Workaround for getSymbolsByUDA not working on structs/classes with private members
208 template getMembersByUDA(T, alias attribute)
209 {
210 	import std.meta : Filter;
211 
212 	enum hasSpecificUDA(string name) = mixin("hasUDA!(T." ~ name ~ ", attribute)");
213 	alias getMembersByUDA = Filter!(hasSpecificUDA, __traits(allMembers, T));
214 }
215 
216 unittest
217 {
218 	writeln("\t * getMembersByUDA");
219 
220 	struct Test
221 	{
222 		@PK int id;
223 		@FK!Test int id2;
224 		@FK!Test int id3;
225 	}
226 
227 	static assert(getMembersByUDA!(Test, PrimaryKeyAttribute)[0] == "id");
228 	static assert(getMembersByUDA!(Test, ForeignKeyAttribute)[0] == "id2");
229 	static assert(getMembersByUDA!(Test, ForeignKeyAttribute)[1] == "id3");
230 }
231 
232 template primaryKeyName(T)
233 {
234 	alias fields = getMembersByUDA!(T, PrimaryKeyAttribute);
235 	static assert(fields.length < 2,
236 			"Multiple or composite primary key found for " ~ T.stringof ~ ", this is not currently supported");
237 	static assert(fields.length == 1, "No primary key found for " ~ T.stringof);
238 
239 	enum primaryKeyName = mixin(fields[0].stringof);
240 }
241 
242 unittest
243 {
244 	writeln("\t * primaryKeyName");
245 	struct Test
246 	{
247 		@PK int myPK;
248 		int id;
249 	}
250 
251 	static assert(primaryKeyName!Test == "myPK");
252 }
253 
254 template isPK(alias T, string m)
255 {
256 	enum isPK = hasUDA!(mixin("T." ~ m), PrimaryKeyAttribute);
257 }
258 
259 unittest
260 {
261 	writeln("\t * isPK");
262 	struct Test
263 	{
264 		@PK int id;
265 		string a;
266 	}
267 	
268 	static assert(isPK!(Test, "id"));
269 	static assert(!isPK!(Test, "a"));
270 }
271 
272 template embeddedPrefix(T)
273 {
274 	enum embeddedPrefix =  "_" ~ relationName!T ~ "_";
275 }
276 
277 template AttributeList2(
278 		T,
279 		string prefix = "",
280 		string asPrefix = "",
281 		bool ignorePK = false,
282 		bool insert = false,
283 		fields...)
284 {
285 	static if (fields.length == 0)
286 		enum AttributeList2 = [];
287 	else
288 	{
289 		alias mt = typeof(mixin("T." ~ fields[0]));
290 
291 		static if (ignorePK && isPK!(T, fields[0]))
292 			enum AttributeList2 = AttributeList2!(T, prefix, asPrefix, ignorePK, insert, fields[1 .. $]);
293 		else static if (ShouldRecurse!(mixin("T." ~ fields[0])))
294 		{
295 			static if (insert)
296 				enum pref = "\"" ~ attributeName!(mixin("T." ~ fields[0])) ~ "\".";
297 			else
298 				enum pref = "(\"" ~ attributeName!(mixin("T." ~ fields[0])) ~ "\").";
299 
300 			alias mType = typeof(mixin("T." ~ fields[0]));
301 			enum AttributeList2 = AttributeList2!(
302 					mType,
303 					pref ~ prefix,
304 					embeddedPrefix!mType ~ asPrefix,
305 					ignorePK,
306 					insert,
307 					serialisableMembers!(typeof(mixin("T." ~ fields[0])))) ~
308 			AttributeList2!(T, prefix, asPrefix, ignorePK, insert, fields[1 .. $]);
309 		}
310 		else
311 		{
312 			enum attrName = attributeName!(mixin("T." ~ fields[0]));
313 			enum AttributeList2 = 
314 					[Column(prefix ~ attrName, asPrefix ~ attrName)] ~ 
315 					AttributeList2!(
316 							T,
317 							prefix,
318 							asPrefix,
319 							ignorePK,
320 							insert,
321 							fields[1 .. $]);
322 		}
323 	}
324 }
325 
326 unittest
327 {
328 	writeln("\t * AttributeList");
329 	struct Test2
330 	{
331 		string bar;
332 		string baz;
333 	}
334 
335 	struct Test
336 	{
337 		@PK int id;
338 		@embed Test2 inner;
339 	}
340 
341 	pragma(msg, ShouldRecurse!(Test.inner));
342 	static assert(AttributeList!Test[0] == Column("id", "id"));
343 	static assert(AttributeList!Test[1] == Column("(\"inner\").bar", "_test2_bar"));
344 	static assert(AttributeList!Test[2] == Column("(\"inner\").baz", "_test2_baz"));
345 
346 	// ignorePK
347 	static assert(AttributeList!(Test, true)[0] == Column("(\"inner\").bar", "_test2_bar"));
348 	static assert(AttributeList!(Test, true)[1] == Column("(\"inner\").baz", "_test2_baz"));
349 
350  // INSERT syntax, with ignorePK
351 	static assert(AttributeList!(Test, true, true)[0] == Column("\"inner\".bar", "_test2_bar"));
352 	static assert(AttributeList!(Test, true, true)[1] == Column("\"inner\".baz", "_test2_baz"));
353 }
354 
355 template AttributeList(T, bool ignorePK = false, bool insert = false)
356 {
357 	alias AttributeList = AttributeList2!(T, "", "", ignorePK, insert, serialisableMembers!(T));
358 }
359 
360 deprecated("Use compile-time AttributeList!T instead")
361 	Column[] attributeList(T)(bool ignorePK = false, bool insert = false) pure
362 {
363 	alias TU = Unqual!T;
364 	Column[] res;
365 
366 	void addMems(T)(string prefix = "", string asPrefix = "")
367 	{
368 		import std.string : format;
369 		foreach(m; serialisableMembers!T)
370 		{
371 			alias mType = typeof(mixin("T." ~ m));
372 			alias attrName = attributeName!(mixin("T." ~ m));
373 			static if (is(mType == class) || is(mType == struct))
374 			{
375 				if (insert)
376 					addMems!mType("\"%s\".%s".format(attrName, prefix));
377 				else
378 					addMems!mType("(\"%s\").%s".format(attrName, prefix), embeddedPrefix!mType);
379 			}
380 			else
381 			{
382 				if (ignorePK && isPK!(T, m))
383 					continue;
384 
385 				res ~= Column("%s\"%s\"".format(prefix, attributeName!(mixin("T." ~ m))),
386 						asPrefix ~ attrName);
387 			}
388 		}
389 	}
390 	addMems!TU;
391 
392 	return res;
393 }
394 alias sqlMembers = attributeList;
395 
396 template serialisableMembers(T)
397 {
398 	alias serialisableMembers = filterSerialisableMembers!(T, __traits(allMembers, T));
399 }
400 
401 unittest
402 {
403 	writeln("\t * serialisableMembers");
404 	struct Test
405 	{
406 		int a;
407 		private int _b;
408 		@property int b() {return _b;};
409 		@property void b(int x) {_b = x;};
410 		@property int c() {return _b;};
411 	}
412 
413 	static assert(serialisableMembers!Test.length == 2);
414 	static assert(serialisableMembers!Test[0] == "a");
415 	static assert(serialisableMembers!Test[1] == "b");
416 }
417 
418 template filterSerialisableMembers(T, FIELDS...)
419 {
420 	static if (FIELDS.length > 1) {
421 		alias filterSerialisableMembers = TypeTuple!(
422 			filterSerialisableMembers!(T, FIELDS[0 .. $/2]),
423 			filterSerialisableMembers!(T, FIELDS[$/2 .. $]));
424 	} else static if (FIELDS.length == 1) {
425 		//alias T = T;
426 		enum mname = FIELDS[0];
427 		static if (isRWPlainField!(T, mname) || isRWField!(T, mname)) 
428 		{
429 			alias tup = TypeTuple!(__traits(getMember, T, FIELDS[0]));
430 			static if (tup.length != 1) 
431 			{
432 				alias filterSerialisableMembers = TypeTuple!(mname);
433 			}
434 			else 
435 			{
436 				static if (!hasUDA!(IgnoreAttribute, __traits(getMember, T, mname)))
437 					alias filterSerialisableMembers = TypeTuple!(mname);
438 				else
439 					alias filterSerialisableMembers = TypeTuple!();
440 			}
441 		} 
442 		else 
443 			alias filterSerialisableMembers = TypeTuple!();
444 	} 
445 	else 
446 		alias filterSerialisableMembers = TypeTuple!();
447 }
448 
449 
450 /*
451 	 Functions below
452 
453 	Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos,
454 	some are dirty hacks that work only for vibe.d
455 	Copyright: © 2012 RejectedSoftware e.K.
456 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
457 	Authors: Sönke Ludwig, Михаил Страшун
458 */
459 
460 
461 /**
462 	Determins if a member is a public, non-static data field.
463 */
464 
465 template isRWPlainField(T, string M)
466 {
467 	static if (!isRWField!(T, M)) 
468 		enum isRWPlainField = false;
469 	else 
470 		enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M)));
471 }
472 
473 /**
474 	Determines if a member is a public, non-static, de-facto data field.
475 	In addition to plain data fields, R/W properties are also accepted.
476 */
477 template isRWField(T, string M)
478 {
479 	import std.traits;
480 	import std.typetuple;
481 
482 	static void testAssign()() 
483 	{
484 		T t = void;
485 		__traits(getMember, t, M) = __traits(getMember, t, M);
486 	}
487 
488 	// reject type aliases
489 	static if (is(TypeTuple!(__traits(getMember, T, M))))
490 		enum isRWField = false;
491 	// reject non-public members
492 	else static if (!isPublicMember!(T, M))
493 		enum isRWField = false;
494 	// reject static members
495 	else static if (!isNonStaticMember!(T, M))
496 		enum isRWField = false;
497 	// reject non-typed members
498 	else static if (!is(typeof(__traits(getMember, T, M))))
499 		enum isRWField = false;
500 	// reject void typed members (includes templates)
501 	else static if (is(typeof(__traits(getMember, T, M)) == void))
502 		enum isRWField = false;
503 	// reject non-assignable members
504 	else static if (!__traits(compiles, testAssign!()()))
505 		enum isRWField = false;
506 	else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M)))
507 	{
508 		// If M is a function, reject if not @property or returns by ref
509 		private enum FA = functionAttributes!(__traits(getMember, T, M));
510 		enum isRWField = (FA & FunctionAttribute.property) != 0;
511 	}
512 	else
513 	{
514 		enum isRWField = true;
515 	}
516 }
517 
518 template isPublicMember(T, string M)
519 {
520 	import std.algorithm, std.typetuple : TypeTuple;
521 
522 	static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) 
523 		enum isPublicMember = false;
524 	else 
525 	{
526 		alias MEM = TypeTuple!(__traits(getMember, T, M));
527 		enum isPublicMember = __traits(getProtection, MEM).among("public", "export");
528 	}
529 }
530 
531 template isNonStaticMember(T, string M)
532 {
533 	import std.typetuple;
534 	import std.traits;
535 
536 	alias MF = TypeTuple!(__traits(getMember, T, M));
537 	static if (M.length == 0) {
538 		enum isNonStaticMember = false;
539 	} else static if (anySatisfy!(isSomeFunction, MF)) {
540 		enum isNonStaticMember = !__traits(isStaticFunction, MF);
541 	} else {
542 		enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }());
543 	}
544 }
545