1 /*
2  * Copyright (C) 2019, HuntLabs
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 module hunt.database.base.Row;
18 
19 import hunt.database.base.Annotations;
20 import hunt.database.base.Tuple;
21 
22 import hunt.logging;
23 
24 
25 import std.array;
26 import std.format;
27 import std.functional;
28 import std.meta;
29 import std.traits;
30 import std.typecons : Nullable;
31 import std.variant;
32 
33 /**
34  * 
35  */
36 interface Row : Tuple {
37 
38     /**
39      * Get a column name at {@code pos}.
40      *
41      * @param pos the column position
42      * @return the column name or {@code null}
43      */
44     string getColumnName(int pos);
45 
46     /**
47      * Get a column position for the given column {@code name}.
48      *
49      * @param name the column name
50      * @return the column name or {@code -1} if not found
51      */
52     int getColumnIndex(string name);
53     
54     /**
55      * Get an object value at {@code pos}.
56      *
57      * @param name the column
58      * @return the value or {@code null}
59      */
60     Variant getValue(string name);
61     alias getValue = Tuple.getValue;
62     alias opIndex = getValue;
63 
64     // Variant opIndex(string name);
65     // alias opIndex = Tuple.opIndex;
66 
67     /**
68      * Get a boolean value at {@code pos}.
69      *
70      * @param name the column
71      * @return the value or {@code null}
72      */
73     bool getBoolean(string name);
74     alias getBoolean = Tuple.getBoolean;
75 
76 
77     /**
78      * Get a short value at {@code pos}.
79      *
80      * @param name the column
81      * @return the value or {@code null}
82      */
83     short getShort(string name);
84     alias getShort = Tuple.getShort;
85 
86     /**
87      * Get an integer value at {@code pos}.
88      *
89      * @param name the column
90      * @return the value or {@code null}
91      */
92     int getInteger(string name);
93     alias getInteger = Tuple.getInteger;
94 
95     /**
96      * Get a long value at {@code pos}.
97      *
98      * @param name the column
99      * @return the value or {@code null}
100      */
101     long getLong(string name);
102     alias getLong = Tuple.getLong;
103 
104     /**
105      * Get a float value at {@code pos}.
106      *
107      * @param name the column
108      * @return the value or {@code null}
109      */
110     float getFloat(string name);
111     alias getFloat = Tuple.getFloat;
112 
113     /**
114      * Get a double value at {@code pos}.
115      *
116      * @param name the column
117      * @return the value or {@code null}
118      */
119     double getDouble(string name);
120     alias getDouble = Tuple.getDouble;
121 
122     /**
123      * Get a string value at {@code pos}.
124      *
125      * @param name the column
126      * @return the value or {@code null}
127      */
128     string getString(string name);
129     alias getString = Tuple.getString;
130 
131     /**
132      * Get a buffer value at {@code pos}.
133      *
134      * @param name the column
135      * @return the value or {@code null}
136      */
137     // Buffer getBuffer(string name);
138     byte[] getBuffer(string name);
139     alias getBuffer = Tuple.getBuffer;
140 
141     // /**
142     //  * Get a temporal value at {@code pos}.
143     //  *
144     //  * @param name the column
145     //  * @return the value or {@code null}
146     //  */
147     // Temporal getTemporal(string name);
148 
149     // /**
150     //  * Get {@link java.time.LocalDate} value at {@code pos}.
151     //  *
152     //  * @param name the column
153     //  * @return the value or {@code null}
154     //  */
155     // LocalDate getLocalDate(string name);
156 
157     // /**
158     //  * Get {@link java.time.LocalTime} value at {@code pos}.
159     //  *
160     //  * @param name the column
161     //  * @return the value or {@code null}
162     //  */
163     // LocalTime getLocalTime(string name);
164 
165     // /**
166     //  * Get {@link java.time.LocalDateTime} value at {@code pos}.
167     //  *
168     //  * @param name the column
169     //  * @return the value or {@code null}
170     //  */
171     // LocalDateTime getLocalDateTime(string name);
172 
173     // /**
174     //  * Get {@link java.time.OffsetTime} value at {@code pos}.
175     //  *
176     //  * @param name the column
177     //  * @return the value or {@code null}
178     //  */
179     // OffsetTime getOffsetTime(string name);
180 
181     // /**
182     //  * Get {@link java.time.OffsetDateTime} value at {@code pos}.
183     //  *
184     //  * @param name the column
185     //  * @return the value or {@code null}
186     //  */
187     // OffsetDateTime getOffsetDateTime(string name);
188 
189     // /**
190     //  * Get {@link java.util.UUID} value at {@code pos}.
191     //  *
192     //  * @param name the column
193     //  * @return the value or {@code null}
194     //  */
195     // UUID getUUID(string name);
196 
197     // /**
198     //  * Get {@link BigDecimal} value at {@code pos}.
199     //  *
200     //  * @param name the column
201     //  * @return the value or {@code null}
202     //  */
203     // BigDecimal getBigDecimal(string name);
204 
205     // /**
206     //  * Get an array of {@link Integer} value at {@code pos}.
207     //  *
208     //  * @param name the column
209     //  * @return the value or {@code null}
210     //  */
211     // Integer[] getIntegerArray(string name);
212 
213     // /**
214     //  * Get an array of {@link Boolean} value at {@code pos}.
215     //  *
216     //  * @param name the column
217     //  * @return the value or {@code null}
218     //  */
219     // Boolean[] getBooleanArray(string name);
220 
221     // /**
222     //  * Get an array of {@link Short} value at {@code pos}.
223     //  *
224     //  * @param name the column
225     //  * @return the value or {@code null}
226     //  */
227     // Short[] getShortArray(string name);
228 
229     // /**
230     //  * Get an array of {@link Long} value at {@code pos}.
231     //  *
232     //  * @param name the column
233     //  * @return the value or {@code null}
234     //  */
235     // Long[] getLongArray(string name);
236 
237     // /**
238     //  * Get an array of {@link Float} value at {@code pos}.
239     //  *
240     //  * @param name the column
241     //  * @return the value or {@code null}
242     //  */
243     // Float[] getFloatArray(string name);
244 
245     // /**
246     //  * Get an array of {@link Double} value at {@code pos}.
247     //  *
248     //  * @param name the column
249     //  * @return the value or {@code null}
250     //  */
251     // Double[] getDoubleArray(string name);
252 
253     // /**
254     //  * Get an array of {@link string} value at {@code pos}.
255     //  *
256     //  * @param name the column
257     //  * @return the value or {@code null}
258     //  */
259     // string[] getStringArray(string name);
260 
261     // /**
262     //  * Get an array of {@link LocalDate} value at {@code pos}.
263     //  *
264     //  * @param name the column
265     //  * @return the value or {@code null}
266     //  */
267     // LocalDate[] getLocalDateArray(string name);
268 
269     // /**
270     //  * Get an array of {@link LocalTime} value at {@code pos}.
271     //  *
272     //  * @param name the column
273     //  * @return the value or {@code null}
274     //  */
275     // LocalTime[] getLocalTimeArray(string name);
276 
277     // /**
278     //  * Get an array of {@link OffsetTime} value at {@code pos}.
279     //  *
280     //  * @param name the column
281     //  * @return the value or {@code null}
282     //  */
283     // OffsetTime[] getOffsetTimeArray(string name);
284 
285     // /**
286     //  * Get an array of {@link LocalDateTime} value at {@code pos}.
287     //  *
288     //  * @param name the column
289     //  * @return the value or {@code null}
290     //  */
291     // LocalDateTime[] getLocalDateTimeArray(string name);
292 
293     // /**
294     //  * Get an array of {@link OffsetDateTime} value at {@code pos}.
295     //  *
296     //  * @param name the column
297     //  * @return the value or {@code null}
298     //  */
299     // OffsetDateTime[] getOffsetDateTimeArray(string name);
300 
301     // /**
302     //  * Get an array of {@link Buffer} value at {@code pos}.
303     //  *
304     //  * @param name the column
305     //  * @return the value or {@code null}
306     //  */
307     // Buffer[] getBufferArray(string name);
308 
309     // /**
310     //  * Get an array of {@link UUID} value at {@code pos}.
311     //  *
312     //  * @param name the column
313     //  * @return the value or {@code null}
314     //  */
315     // UUID[] getUUIDArray(string name);
316 
317     // <T> T[] getValues(Class!(T) type, int idx);
318 
319     alias getAs = bind;
320 
321     final T bind(T, alias getColumnNameFun="b")() if(is(T == struct)) {
322         T r;
323 
324         static if(hasUDA!(T, Table)) {
325             enum tableName = getUDAs!(T, Table)[0].name;
326         } else {
327             enum tableName = T.stringof;
328         }
329 
330         bindObject!(tableName, getColumnNameFun)(r);
331 
332         return r;
333     }
334 
335     final void bindObject(string tableName = T.stringof, 
336             alias getColumnNameFun="b", T)(ref T obj) if(is(T == struct)) {
337         alias getColumnName = binaryFun!getColumnNameFun;
338 
339         // current fields in T
340 		static foreach (string member; FieldNameTuple!T) {{
341             alias currentMember = Alias!(__traits(getMember, T, member));
342             alias memberType = typeof(__traits(getMember, T, member));
343 
344             static if(hasUDA!(currentMember, Ignore)) {
345                 version(HUNT_DEBUG) { warningf("Field %s.%s ignored.", T.stringof, member); }
346             } else static if(is(memberType == class)) { 
347                 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)();
348             } else static if(is(memberType == struct) && !is(memberType : Nullable!U, U)) {
349                 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)();
350             } else {
351                 static if(hasUDA!(currentMember, Column)) {
352                     enum memberColumnAttr = getUDAs!(currentMember, Column)[0];
353                     enum string memberColumnName = memberColumnAttr.name;
354                     static assert(!memberColumnName.empty());
355 
356                     enum int memeberColumnOrder = memberColumnAttr.order;
357                 } else {
358                     enum string memberColumnName = member;
359                     enum int memeberColumnOrder = -1;
360                 }
361 
362                 enum string columnName = getColumnName(tableName, memberColumnName);
363                 __traits(getMember, obj, member) = getValueAs!(columnName, memeberColumnOrder, memberType)();
364             }
365         }}
366     }
367 
368     final T bind(T, bool traverseBase=true, alias getColumnNameFun="b")() 
369             if(is(T == class) && __traits(compiles, new T())) {
370         T r = new T();
371 
372         static if(hasUDA!(T, Table)) {
373             enum tableName = getUDAs!(T, Table)[0].name;
374         } else {
375             enum tableName = T.stringof;
376         }
377 
378         bindObject!(tableName, traverseBase, getColumnNameFun, T)(r); // bug
379         return r;
380     }
381 
382     final void bindObject(string tableName = T.stringof,  bool traverseBase=true,
383             alias getColumnNameFun="b", T)(T obj) if(is(T == class)) {
384         alias getColumnName = binaryFun!getColumnNameFun;
385 
386         // current fields in T
387 		static foreach (string member; FieldNameTuple!T) {{
388             alias currentMember = Alias!(__traits(getMember, T, member));
389             alias memberType = typeof(__traits(getMember, T, member));
390 
391             static if(hasUDA!(currentMember, Ignore)) {
392                 version(HUNT_DEBUG) { warningf("Field %s.%s ignored.", T.stringof, member); }
393             } else static if((is(memberType == struct) && !is(memberType : Nullable!U, U))) {
394                 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)();
395             } else static if(is(memberType == class)) {
396                 __traits(getMember, obj, member) = bind!(memberType, traverseBase,  getColumnNameFun)(); // bug
397             } else {
398                 static if(hasUDA!(currentMember, Column)) {
399                     enum memberColumnAttr = getUDAs!(currentMember, Column)[0];
400                     enum string memberColumnName = memberColumnAttr.name;
401                     static assert(!memberColumnName.empty());
402 
403                     enum int memeberColumnOrder = memberColumnAttr.order;
404                 } else {
405                     enum string memberColumnName = member;
406                     enum int memeberColumnOrder = -1;
407                 }
408 
409                 enum string columnName = getColumnName(tableName, memberColumnName);
410                 __traits(getMember, obj, member) = getValueAs!(columnName, memeberColumnOrder, memberType)();
411             }
412         }}
413 
414         static if(traverseBase) {
415             // all fields in the super of T
416             alias baseClasses = BaseClassesTuple!T;
417             static if(baseClasses.length >= 2) { // skip Object
418                 bindObject!(tableName, traverseBase, getColumnNameFun, baseClasses[0])(obj);
419             }
420         }
421     }
422 
423     final private T getValueAs(string memberColumnName, int memeberColumnOrder = -1, T)() {
424         int columnIndex = memeberColumnOrder;
425         static if(memeberColumnOrder == -1) {
426             columnIndex = getColumnIndex(memberColumnName);
427             if(columnIndex == -1) {
428                 version(HUNT_DEBUG) warningf("Column does not exist: %s", memberColumnName);
429                 return T.init;
430             }
431         }
432 
433         if(columnIndex>=this.size()) {
434             version(HUNT_DEBUG) warningf("Index is out of range: %d>%d", columnIndex, this.size());
435             return T.init;
436         }
437 
438         Variant currentColumnValue = getValue(columnIndex);
439         version(HUNT_DB_DEBUG) {
440             tracef("column, name=%s, index=%d, type {target: %s, source: %s}", 
441                 memberColumnName, columnIndex, T.stringof, currentColumnValue.type);
442         }
443 
444         static if(is(T : Nullable!U, U)) {
445             auto memberTypeInfo = typeid(U);
446             if(memberTypeInfo == currentColumnValue.type || currentColumnValue.convertsTo!(U)) {
447                 // 1) If the types are same, or the column's type can convert to the member's type
448                 U tmp = currentColumnValue.get!U();
449                 return T(tmp);
450             } else if(currentColumnValue == null) {
451                 return T.init;
452             } else {
453                 // 2) try to coerce to T
454                 U tmp = currentColumnValue.coerce!U();
455                 return T(tmp);
456             }
457         } else {
458             auto memberTypeInfo = typeid(T);
459             if(memberTypeInfo == currentColumnValue.type || currentColumnValue.convertsTo!(T)) {
460                 // 1) If the types are same, or the column's type can convert to the member's type
461                 return currentColumnValue.get!T();
462             } else if(currentColumnValue == null) {
463                 return T.init;
464             } else {
465                 // 2) try to coerce to T
466                 return currentColumnValue.coerce!T();
467                 // assert(false, format("Can't convert a value from %s to %s", 
468                 //     currentColumnValue.type, memberTypeInfo));
469             }
470         }
471 
472     }
473 }