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 }