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