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 }