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