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 }