1 /** 2 Copyright: © 2015 Chris Barnes 3 License: The MIT License, see license file 4 Authors: Chris Barnes 5 6 See_also: 7 quill.database 8 */ 9 module quill.mapper; 10 11 import ddbc.all; 12 import std.algorithm; 13 import std.array; 14 import std.conv; 15 import std.datetime; 16 import std.traits; 17 import std.variant; 18 import quill.mapper_base; 19 20 /** 21 Maps parameters, and results to a class specified by T. 22 */ 23 class Mapper(T) : MapperBase 24 { 25 this(ResultSet resultSet) 26 { 27 super(resultSet); 28 } 29 30 this(PreparedStatement statement) 31 { 32 super(statement); 33 } 34 35 string getBindName(T, string property)(T model) 36 { 37 auto attributes = __traits(getAttributes, __traits(getMember, model, property)); 38 39 foreach(index, attribute; attributes) 40 { 41 static if(is(typeof(attribute) == quill.attributes.bind)) 42 { 43 return attribute.bind; 44 } 45 } 46 return ""; 47 } 48 49 /** 50 Maps each property of T to a column in a ResultSet. 51 If T is a class, it will new it up and map all of the public fields. 52 If T is a primitive type it will assume the result of the query boils down to a single value and returns that. 53 Returns: 54 An instance of T or a single primitive value. 55 */ 56 T map() 57 { 58 static if(isSupportedType!(T)()) 59 { 60 return this.resultSet.getRow() > 0 ? this.mapType!(T)(1) : T.init; 61 } 62 else 63 { 64 T model = new T(); 65 if(this.resultSet.getRow() > 0) 66 { 67 foreach(int i, property;__traits(allMembers, T)) 68 { 69 int columnIndex; 70 static if(isPublic!(T, property)) 71 { 72 //Note: must check isPublic first since isIgnored accesses the property 73 static if(!isIgnored!(T, property)()) 74 { 75 string bindName = getBindName!(T, property)(model); 76 if(bindName != "") 77 { 78 columnIndex = this.findColumn(bindName); 79 } 80 if(columnIndex == 0) 81 { 82 columnIndex = this.findColumn(property); 83 } 84 if(columnIndex != 0) { 85 static if(T.tupleof.length > i) 86 { 87 __traits(getMember, model, property) = this.mapType!(typeof(T.tupleof[i]))(columnIndex); 88 } 89 } 90 } 91 } 92 } 93 return model; 94 } 95 else 96 { 97 return null; 98 } 99 } 100 } 101 102 /** 103 Maps a class into a PreparedStatement 104 105 Params: 106 model = an instance of T with fields that match the parmeter names or provide a bind user defined attribute 107 map = a string array where the index is the parameter index and the string is the parameter name 108 */ 109 void map(T)(T model, string[] map) 110 { 111 foreach(int i, property;__traits(allMembers, T)) 112 { 113 int parameterIndex; 114 static if(isPublic!(T, property)) 115 { 116 //Note: must check isPublic first since isIgnored accesses the property 117 static if(!isIgnored!(T, property)()) 118 { 119 auto attributes = __traits(getAttributes, __traits(getMember, model, property)); 120 parameterIndex = to!(int)(countUntil(map, property)) + 1; 121 122 string bindName = getBindName!(T, property)(model); 123 if(bindName != "") 124 { 125 parameterIndex = to!(int)(countUntil(map, bindName)) + 1; 126 } 127 128 if(parameterIndex > 0) { 129 static if(T.tupleof.length > i) 130 { 131 this.mapType!(typeof(T.tupleof[i]))(parameterIndex, __traits(getMember, model, property)); 132 } 133 } 134 } 135 } 136 } 137 } 138 139 /** 140 Maps an array of models where the ResultSet is expected to have multiple rows 141 */ 142 T[] mapArray() 143 { 144 auto appender = appender!(T[])(); 145 while (this.resultSet.next()) 146 { 147 appender.put(this.map()); 148 } 149 return appender.data; 150 } 151 152 /** 153 Maps a single T where the ResultSet is expected to have only one row. 154 */ 155 T mapOne() 156 { 157 this.resultSet.next(); 158 return this.map(); 159 } 160 161 private: 162 /** 163 Sets a parameter on the PreparedStatement if it is supported 164 */ 165 void mapType(T)(int parameterIndex, T value) 166 { 167 static if(is(T == float)) 168 { 169 this.statement.setFloat(parameterIndex, value); 170 } 171 else static if(is(T == double)) 172 { 173 this.statement.setDouble(parameterIndex, value); 174 } 175 else static if(is(T == bool)) 176 { 177 this.statement.setBoolean(parameterIndex, value); 178 } 179 else static if(is(T == long)) 180 { 181 this.statement.setLong(parameterIndex, value); 182 } 183 else static if(is(T == ulong)) 184 { 185 this.statement.setUlong(parameterIndex, value); 186 } 187 else static if(is(T == int)) 188 { 189 this.statement.setInt(parameterIndex, value); 190 } 191 else static if(is(T == uint)) 192 { 193 this.statement.setUint(parameterIndex, value); 194 } 195 else static if(is(T == short)) 196 { 197 this.statement.setShort(parameterIndex, value); 198 } 199 else static if(is(T == ushort)) 200 { 201 this.statement.setUshort(parameterIndex, value); 202 } 203 else static if(is(T == byte)) 204 { 205 this.statement.setByte(parameterIndex, value); 206 } 207 else static if(is(T == ubyte)) 208 { 209 this.statement.setUbyte(parameterIndex, value); 210 } 211 else static if(is(T == byte[])) 212 { 213 this.statement.setBytes(parameterIndex, value); 214 } 215 else static if(is(T == ubyte[])) 216 { 217 this.statement.setUbytes(parameterIndex, value); 218 } 219 else static if(is(T == string)) 220 { 221 this.statement.setString(parameterIndex, value); 222 } 223 else static if(is(T == DateTime)) 224 { 225 this.statement.setDateTime(parameterIndex, value); 226 } 227 else 228 { 229 this.statement.setNull(parameterIndex); 230 } 231 } 232 233 /** 234 Gets a value out of the current row if it is supported. 235 */ 236 T mapType(T)(int columnIndex) 237 { 238 static if(is(T == float)) 239 { 240 return this.resultSet.getFloat(columnIndex); 241 } 242 else static if(is(T == double)) 243 { 244 return this.resultSet.getDouble(columnIndex); 245 } 246 else static if(is(T == bool)) 247 { 248 return this.resultSet.getBoolean(columnIndex); 249 } 250 else static if(is(T == long)) 251 { 252 return this.resultSet.getLong(columnIndex); 253 } 254 else static if(is(T == ulong)) 255 { 256 return this.resultSet.getUlong(columnIndex); 257 } 258 else static if(is(T == int)) 259 { 260 return this.resultSet.getInt(columnIndex); 261 } 262 else static if(is(T == uint)) 263 { 264 return this.resultSet.getUint(columnIndex); 265 } 266 else static if(is(T == short)) 267 { 268 return this.resultSet.getShort(columnIndex); 269 } 270 else static if(is(T == ushort)) 271 { 272 return this.resultSet.getUshort(columnIndex); 273 } 274 else static if(is(T == byte)) 275 { 276 return this.resultSet.getByte(columnIndex); 277 } 278 else static if(is(T == ubyte)) 279 { 280 return this.resultSet.getUbyte(columnIndex); 281 } 282 else static if(is(T == byte[])) 283 { 284 return this.resultSet.getBytes(columnIndex); 285 } 286 else static if(is(T == ubyte[])) 287 { 288 return this.resultSet.getUbytes(columnIndex); 289 } 290 else static if(is(T == string)) 291 { 292 return this.resultSet.getString(columnIndex); 293 } 294 else static if(is(T == DateTime)) 295 { 296 return this.resultSet.getDateTime(columnIndex); 297 } 298 else 299 { 300 return null; 301 } 302 } 303 } 304 305 /** 306 Checks if T is one of the supported primitive types 307 */ 308 bool isSupportedType(T)() 309 { 310 return is(T == float) || is(T == double) || is(T == bool) || is(T == long) || is(T == ulong) || is(T == int) || 311 is(T == uint) || is(T == short) || is(T == ushort) || is(T == byte) || is(T == ubyte) || is(T == byte[]) || 312 is(T == ubyte[]) || is(T == string) || is(T == DateTime); 313 314 } 315 316 /** 317 Checks if the property of T has the @omit attribute 318 */ 319 bool isIgnored(T, string property)() 320 { 321 auto attributes = __traits(getAttributes, __traits(getMember, T, property)); 322 foreach(index, attribute; attributes) 323 { 324 if(attribute.to!string == quill.attributes.omit) 325 { 326 return true; 327 } 328 } 329 return false; 330 } 331 332 /** 333 Checks if the property of T is public 334 */ 335 bool isPublic(T, string property)() 336 { 337 static if(__traits(getProtection, __traits(getMember, T, property)) == "public") 338 { 339 return true; 340 } 341 else 342 { 343 return false; 344 } 345 }