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