1 module dorm.types.patches; 2 3 import dorm.api.db; 4 import dorm.declarative.conversion; 5 import dorm.model; 6 7 import std.meta; 8 import std.traits; 9 10 /** 11 * UDA to mark patch structs with, to make selecting them easier. 12 * 13 * Examples: 14 * --- 15 * @DormPatch!User 16 * struct UserSelection 17 * { 18 * string username; 19 * } 20 * db.select!UserSelection; 21 * 22 * // is equivalent to 23 * struct UserSelection 24 * { 25 * string username; 26 * } 27 * db.select!(User, UserSelection); 28 * 29 * // is equivalent to 30 * db.select!(User, "username"); 31 * 32 * // is equivalent to 33 * db.select!(User, User.username); 34 * 35 * // is equivalent to 36 * db.select!(User, Tuple!(string, "username")); 37 * --- 38 */ 39 struct DormPatch(User) 40 { 41 } 42 43 /// Helper to validate if a DormPatch annotated field is valid for the given 44 /// Model type. 45 mixin template ValidatePatch(Patch, TModel) 46 { 47 import std.traits : hasUDA; 48 import dorm.annotations : isDormFieldAttribute; 49 50 static assert(is(TModel : Model), "Passed non-Model to ValidatePatch"); 51 52 static if (!is(Patch == TModel) && !isImplicitPatch!(TModel, Patch)) 53 { 54 static assert (hasUDA!(Patch, DormPatch!TModel), "Patch struct " ~ Patch.stringof 55 ~ " must be annoated using DormPatch!(" ~ TModel.stringof ~ ") exactly once!"); 56 57 static foreach (i, field; Patch.tupleof) 58 { 59 static assert (__traits(hasMember, TModel.init, Patch.tupleof[i].stringof), 60 "\n" ~ SourceLocation(__traits(getLocation, Patch.tupleof[i])).toErrorString 61 ~ "Patch field `" ~ Patch.tupleof[i].stringof 62 ~ "` is not defined on DB Type " ~ TModel.stringof 63 ~ ".\n\tAvailable usable fields: " 64 ~ DormFields!TModel.map!(f => f.sourceColumn).join(", ")); 65 66 static foreach (attr; __traits(getAttributes, Patch.tupleof[i])) 67 static assert (!isDormFieldAttribute!attr, 68 "\n" ~ SourceLocation(__traits(getLocation, Patch.tupleof[i])).toErrorString 69 ~ "Patch field `" ~ Patch.tupleof[i].stringof 70 ~ "` defines DB-related annotations, which is not" 71 ~ " supported. Put annotations on the Model field instead!"); 72 } 73 } 74 } 75 76 /// Checks if Patch is an implicit patch of TModel. That is a child struct of 77 /// the given model class. 78 enum isImplicitPatch(TModel, Patch) = is(__traits(parent, Patch) == TModel); 79 80 template ImplicitPatchFieldName(TModel, Patch) 81 { 82 static assert(isImplicitPatch!(TModel, Patch)); 83 84 static foreach (member; TModel.tupleof) 85 { 86 static if (is(typeof(member) == Patch)) 87 { 88 enum ImplicitPatchFieldName = __traits(identifier, member); 89 } 90 } 91 } 92 93 /// Checks if the given type Patch is either an implicit or an explicit patch. 94 enum isSomePatch(Patch) = 95 is(Patch == struct) && (is(__traits(parent, Patch) : Model) 96 || hasUDA!(Patch, DormPatch)); 97 enum isSomePatch(alias other) = false; 98 99 static template PatchFromIdOrPatch(alias idOrPatch) 100 { 101 static if (is(idOrPatch : Model)) 102 alias PatchFromIdOrPatch = idOrPatch; 103 else static if (isSomePatch!idOrPatch) 104 alias PatchFromIdOrPatch = idOrPatch; 105 else static if (is(__traits(parent, idOrPatch) : Model) 106 || isSomePatch!(__traits(parent, idOrPatch))) 107 alias PatchFromIdOrPatch = __traits(parent, idOrPatch); 108 else 109 static assert(false, "Invalid id or patch: " ~ idOrPatch.stringof); 110 } 111 112 static template IdAliasFromIdOrPatch(alias idOrPatch) 113 { 114 static if (is(idOrPatch : Model)) 115 alias IdAliasFromIdOrPatch = 116 __traits(getMember, idOrPatch, DormPrimaryKeyName!idOrPatch); 117 else static if (isSomePatch!idOrPatch) 118 alias IdAliasFromIdOrPatch = 119 __traits(getMember, idOrPatch, 120 DormPrimaryKeyName!(ModelFromSomePatch!idOrPatch)); 121 else static if (is(__traits(parent, idOrPatch) : Model) 122 || isSomePatch!(__traits(parent, idOrPatch))) 123 alias IdAliasFromIdOrPatch = idOrPatch; 124 else 125 static assert(false, "Invalid id or patch: " ~ idOrPatch.stringof); 126 } 127 128 static template ModelFromIdOrModel(alias idOrModel) 129 { 130 static if (is(idOrModel : Model)) 131 alias ModelFromIdOrModel = idOrModel; 132 else static if (is(__traits(parent, idOrModel) : Model)) 133 alias ModelFromIdOrModel = __traits(parent, idOrModel); 134 else 135 static assert(false, "Invalid id or model: " ~ idOrModel.stringof); 136 } 137 138 static template IdAliasFromIdOrModel(alias idOrModel) 139 { 140 static if (is(idOrModel : Model)) 141 alias IdAliasFromIdOrModel = 142 __traits(getMember, idOrModel, DormPrimaryKeyName!idOrModel); 143 else static if (is(__traits(parent, idOrModel) : Model)) 144 alias IdAliasFromIdOrModel = idOrModel; 145 else 146 static assert(false, "Invalid id or model: " ~ idOrModel.stringof); 147 } 148 149 static template ModelFromSomePatch(TModel) 150 { 151 static if (is(__traits(parent, TModel) : Model)) 152 alias ModelFromSomePatch = __traits(parent, TModel); 153 else 154 alias ModelFromSomePatch = DBType!TModel; 155 } 156 157 static template TypeOfField(TModel, alias field) 158 { 159 static if (is(__traits(parent, field) == TModel) 160 || is(__traits(parent, __traits(parent, field)) == TModel)) 161 alias TypeOfField = typeof(field); 162 else 163 static assert(false, 164 "Can't use field " ~ field.stringof ~ " on Model type " ~ TModel.stringof); 165 } 166 167 template DBType(Selection...) 168 { 169 static assert(Selection.length >= 1); 170 171 static if (Selection.length > 1) 172 { 173 alias DBType = GetModelDBType!(Selection[0]); 174 } 175 else 176 { 177 alias PatchAttrs = getUDAs!(Selection[0], DormPatch); 178 static if (PatchAttrs.length == 0) 179 alias DBType = GetModelDBType!(Selection[0]); 180 else static if (PatchAttrs.length == 1) 181 { 182 static if (is(PatchAttrs[0] == DormPatch!T, T)) 183 { 184 mixin ValidatePatch!(Selection[0], T); 185 alias DBType = T; 186 } 187 else 188 static assert(false, "internal template error"); 189 } 190 else 191 static assert(false, "Cannot annotate DormPatch struct with multiple DormPatch UDAs."); 192 } 193 } 194 195 template GetModelDBType(T) 196 { 197 import dorm.model : Model; 198 199 static if (is(T : Model)) 200 alias GetModelDBType = T; 201 else static if (is(__traits(parent, T) : Model)) 202 alias GetModelDBType = __traits(parent, T); 203 else 204 static assert(false, "Passed in Non-Model Type where a Model was expected"); 205 } 206 207 template SelectType(T, Selection...) 208 { 209 import std.traits : isAggregateType; 210 211 static if (Selection.length == 0) 212 alias SelectType = T; 213 else static if (Selection.length == 1 && isAggregateType!(Selection[0])) 214 alias SelectType = Selection[0]; 215 else 216 alias SelectType = BuildFieldsTuple!(T, Selection); 217 } 218 219 template BuildFieldsTuple(T, Selection...) 220 { 221 import std.meta : AliasSeq; 222 import std.typecons : Tuple; 223 224 alias TupleArgs = AliasSeq!(); 225 static foreach (alias Field; Selection) 226 { 227 static if (__traits(compiles, { string s = Field; })) 228 alias TupleArgs = AliasSeq!(TupleArgs, typeof(__traits(getMember, T, Field)), Field); 229 else 230 alias TupleArgs = AliasSeq!(TupleArgs, typeof(Field), __traits(identifier, Field)); 231 } 232 alias BuildFieldsTuple = Tuple!TupleArgs; 233 } 234 235 template FilterLayoutFields(T, TSelect) 236 { 237 static if (is(T == TSelect)) 238 enum FilterLayoutFields = DormFields!T; 239 else static if (is(TSelect : Model)) 240 static assert(false, "Cannot filter for fields of Model class on a Model class"); 241 else static if (isImplicitPatch!(T, TSelect)) 242 enum FilterLayoutFields = filterFieldsPrefixed!T( 243 ImplicitPatchFieldName!(T, TSelect) ~ ".", 244 implicitPatchSelectionFieldNames!(T, TSelect) 245 ); 246 else 247 enum FilterLayoutFields = filterFields!T(selectionFieldNames!(T, TSelect)); 248 } 249 250 private auto filterFields(T)(string[] sourceNames...) 251 { 252 import std.algorithm : canFind; 253 254 enum fields = DormFields!T; 255 typeof(fields) ret; 256 foreach (ref field; fields) 257 if (sourceNames.canFind(field.sourceColumn)) 258 ret ~= field; 259 return ret; 260 } 261 262 private auto filterFieldsPrefixed(T)(string prefix, string[] sourceNames...) 263 { 264 import std.algorithm : canFind, startsWith; 265 266 enum fields = DormFields!T; 267 typeof(fields) ret; 268 foreach (ref field; fields) 269 if (field.sourceColumn.startsWith(prefix) 270 && sourceNames.canFind(field.sourceColumn[prefix.length .. $])) 271 { 272 field.sourceColumn = field.sourceColumn[prefix.length .. $]; 273 ret ~= field; 274 } 275 return ret; 276 } 277 278 private string[] implicitPatchSelectionFieldNames(T, TSelect)() 279 { 280 static foreach (field; __traits(allMembers, T)) 281 static if (field == ImplicitPatchFieldName!(T, TSelect)) 282 return selectionFieldNames!(T, TSelect)(); 283 } 284 285 private string[] selectionFieldNames(T, TSelect)(string prefix = "") 286 { 287 import std.algorithm : canFind; 288 289 enum layout = DormLayout!T; 290 291 string[] ret; 292 static foreach (field; __traits(allMembers, TSelect)) 293 { 294 static if (layout.embeddedStructs.canFind(field)) 295 ret ~= selectionFieldNames!(T, typeof(__traits(getMember, TSelect, field)))( 296 prefix ~ field ~ "."); 297 else 298 ret ~= (prefix ~ field); 299 } 300 return ret; 301 }