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 } 496 else 497 alias filterSerialisableMembers = filterSerialisableMembers!(T, fields[1 .. $]); 498 } 499 } 500 501 502 /* 503 Functions/templates isRWPlainField, isRWField, isPublicMember and isNonStaticMember. 504 505 Extensions to `std.traits` module of Phobos. Some may eventually make it into Phobos, 506 some are dirty hacks that work only for vibe.d 507 Copyright: © 2012 RejectedSoftware e.K. 508 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 509 Authors: Sönke Ludwig, Михаил Страшун 510 */ 511 512 513 /** 514 Determins if a member is a public, non-static data field. 515 */ 516 517 template isRWPlainField(T, string M) 518 { 519 static if (!isRWField!(T, M)) 520 enum isRWPlainField = false; 521 else 522 enum isRWPlainField = __traits(compiles, *(&__traits(getMember, Tgen!T(), M)) = *(&__traits(getMember, Tgen!T(), M))); 523 } 524 525 /** 526 Determines if a member is a public, non-static, de-facto data field. 527 In addition to plain data fields, R/W properties are also accepted. 528 */ 529 template isRWField(T, string M) 530 { 531 import std.traits; 532 import std.typetuple; 533 534 static void testAssign()() 535 { 536 T t = void; 537 __traits(getMember, t, M) = __traits(getMember, t, M); 538 } 539 540 // reject type aliases 541 static if (is(TypeTuple!(__traits(getMember, T, M)))) 542 enum isRWField = false; 543 // reject non-public members 544 else static if (!isPublicMember!(T, M)) 545 enum isRWField = false; 546 // reject static members 547 else static if (!isNonStaticMember!(T, M)) 548 enum isRWField = false; 549 // reject non-typed members 550 else static if (!is(typeof(__traits(getMember, T, M)))) 551 enum isRWField = false; 552 // reject void typed members (includes templates) 553 else static if (is(typeof(__traits(getMember, T, M)) == void)) 554 enum isRWField = false; 555 // reject non-assignable members 556 else static if (!__traits(compiles, testAssign!()())) 557 enum isRWField = false; 558 else static if (anySatisfy!(isSomeFunction, __traits(getMember, T, M))) 559 { 560 // If M is a function, reject if not @property or returns by ref 561 private enum FA = functionAttributes!(__traits(getMember, T, M)); 562 enum isRWField = (FA & FunctionAttribute.property) != 0; 563 } 564 else 565 { 566 enum isRWField = true; 567 } 568 } 569 570 template isPublicMember(T, string M) 571 { 572 import std.algorithm, std.typetuple : TypeTuple; 573 574 static if (!__traits(compiles, TypeTuple!(__traits(getMember, T, M)))) 575 enum isPublicMember = false; 576 else 577 { 578 alias MEM = TypeTuple!(__traits(getMember, T, M)); 579 enum isPublicMember = __traits(getProtection, MEM).among("public", "export"); 580 } 581 } 582 583 template isNonStaticMember(T, string M) 584 { 585 import std.typetuple; 586 import std.traits; 587 588 alias MF = TypeTuple!(__traits(getMember, T, M)); 589 590 static if (M.length == 0) 591 { 592 enum isNonStaticMember = false; 593 } 594 else static if (anySatisfy!(isSomeFunction, MF)) 595 { 596 enum isNonStaticMember = !__traits(isStaticFunction, MF); 597 } 598 else 599 { 600 enum isNonStaticMember = !__traits(compiles, (){ auto x = __traits(getMember, T, M); }()); 601 } 602 }