1 /*
2  * Entity - Entity is an object-relational mapping tool for the D programming language. Referring to the design idea of JPA.
3  *
4  * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs.cn
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.entity.eql.EqlQuery;
13 
14 import hunt.entity;
15 
16 import hunt.entity.EntityMetaInfo;
17 import hunt.entity.eql.EqlParse;
18 import hunt.entity.eql.ResultDes;
19 import hunt.entity.eql.EqlInfo;
20 import hunt.entity.eql.EqlCache;
21 
22 import hunt.sql;
23 import hunt.logging;
24 import hunt.collection;
25 import hunt.Long;
26 
27 // version(WITH_HUNT_TRACE)
28 // {
29 //     import hunt.trace.Constrants;
30 //     import hunt.trace.Plugin;
31 //     import hunt.trace.Span;
32 // }
33 
34 import std.algorithm;
35 import std.conv;
36 import std.format;
37 import std.traits;
38 import std.string;
39 import std.variant;
40 
41 
42 
43 class EqlQuery(T...)
44 {
45     alias executeUpdate = exec;
46     alias ResultObj = T[0];
47 
48     private EntityManager _manager;
49     private ResultDes!(ResultObj) _resultDes;
50 
51     private EqlParse _eqlParser;
52     private string _eql;
53     private string _countEql;
54     private Pageable _pageable;
55     private string[] _isExtracted;
56     private long _offset = -1;
57     private long _limit = -1;
58     private int _lastInsertId = -1;
59     private int _affectRows = 0;
60     private EntityMetaInfo _entityInfo;
61     // private enum EntityMetaInfo _entityInfo = extractEntityInfo!ResultObj();
62 
63     version (WITH_HUNT_TRACE)
64     {
65         private Span _span;
66         private string[string] _tags;
67     }
68 
69     this(string eql, EntityManager em)
70     {
71         _manager = em;
72         _resultDes = new ResultDes!(ResultObj)(em);
73         _eql = eql;
74         _entityInfo = ResultObj.metaInfo; // extractEntityInfo!ResultObj();
75         parseEql();
76     }
77 
78     this(string query_eql, Pageable page, EntityManager em)
79     {
80         _manager = em;
81         _resultDes = new ResultDes!(ResultObj)(em);
82         _eql = query_eql;
83         _pageable = page;
84         parseEql();
85     }
86 
87     version (WITH_HUNT_TRACE)
88     {
89         private void beginTrace(string name)
90         {
91             _tags.clear();
92             _span = traceSpanBefore(name);
93         }
94 
95         private void endTrace(string error = null)
96         {
97             if (_span !is null)
98             {
99                 _tags["eql"] = _eql;
100                 traceSpanAfter(_span, _tags, error);
101             }
102         }
103     }
104 
105     private void parseEql()
106     {
107         version (WITH_HUNT_TRACE)
108         {
109             beginTrace("EQL PARSE");
110             scope (exit)
111                 endTrace();
112         }
113         DatabaseOption opt = _manager.getDbOption();
114         if (opt.isMysql())
115         {
116             _eqlParser = new EqlParse(_eql, _entityInfo, DBType.MYSQL.name);
117         }
118         else if (opt.isPgsql())
119         {
120             _eqlParser = new EqlParse(_eql, _entityInfo, DBType.POSTGRESQL.name);
121         }
122         // else if (opt.isSqlite())
123         // {
124         //     _eqlParser = new EqlParse(_eql, DBType.SQLITE.name);
125         // }
126         else
127         {
128             throw new Exception("not support dbtype : %s".format(opt.schemeName()));
129         }
130         version(HUNT_SQL_DEBUG) {
131             tracef("Raw eql: %s", _eql);
132         }
133 
134         foreach (ObjType; T) {
135             extractInfo!ObjType();
136         }
137 
138         auto parsedEql = eqlCache.get(_eql);
139         if (parsedEql is null)
140         {
141             _eqlParser.parse();
142 
143             eqlCache.put(_eql, _eqlParser.getParsedEql());
144         }
145         else
146         {
147             version(HUNT_ENTITY_DEBUG) trace("EQL Cache Hit");
148             _eqlParser.setParsedEql(parsedEql);
149         }
150 
151         // _countEql = PagerUtils.count(_eqlParser.getNativeSql, _eqlParser.getDBType());
152     }
153 
154     private void extractInfo(ObjType)()
155     {
156         if (_isExtracted.canFind(ObjType.stringof))
157             return;
158         _isExtracted ~= ObjType.stringof;
159 
160         static if (isAggregateType!(ObjType)) //  && hasUDA!(ObjType, Table)
161         {
162             EqlInfo!(ObjType) entInfo = new EqlInfo!(ObjType)(_manager);
163 
164             _eqlParser.putFields(entInfo.getEntityClassName(), entInfo.getFields);
165             _eqlParser.putClsTbName(entInfo.getEntityClassName(), entInfo.getTableName());
166 
167             _eqlParser.putJoinCond(entInfo.getJoinConds());
168             if (ObjType.stringof == ResultObj.stringof)
169             {
170                 _resultDes.setFields(entInfo.getFields);
171             }
172         }
173         else
174         {
175             // throw new Exception(" not support type : " ~ ObjType.stringof);
176         }
177 
178         foreach (memberName; __traits(derivedMembers, ObjType))
179         {
180             static if (__traits(getProtection, __traits(getMember, ObjType, memberName)) == "public")
181             {
182                 alias memType = typeof(__traits(getMember, ObjType, memberName));
183                 static if (is(memType == class))
184                 {
185                     {
186                         auto sub_en = new EqlInfo!(memType)(_manager);
187                         _eqlParser.putFields(sub_en.getEntityClassName(), sub_en.getFields);
188                         _eqlParser.putClsTbName(sub_en.getEntityClassName(), sub_en.getTableName());
189                         _eqlParser._objType[ObjType.stringof ~ "." ~ memberName] = sub_en.getEntityClassName();
190 
191                         extractInfo!memType();
192                     }
193                 }
194                 else if (isArray!memType)
195                 {
196                 }
197             }
198         }
199     }
200 
201     /**
202     idx: It starts from 1. 
203     */
204     public EqlQuery setParameter(R = string)(int idx, R param)
205     {
206         if (_eqlParser !is null)
207         {
208             _eqlParser.setParameter!R(idx, param);
209         }
210         return this;
211     }
212 
213     public EqlQuery setParameter(R = string)(string idx, R param)
214     {
215         if (_eqlParser !is null)
216         {
217             _eqlParser.setParameter!R(idx, param);
218         }
219         return this;
220     }
221 
222     public EqlQuery setMaxResults(long maxResult)
223     {
224         if (_pageable)
225         {
226             throw new Exception("This method is not supported!");
227         }
228         _limit = maxResult;
229         return this;
230     }
231 
232     public EqlQuery setFirstResult(long startPosition)
233     {
234         if (_pageable)
235         {
236             throw new Exception("This method is not supported!");
237         }
238         _offset = startPosition;
239         return this;
240     }
241 
242     public string getExecSql()
243     {
244         auto sql = strip(_eqlParser.getNativeSql());
245         if (endsWith(sql, ";"))
246             sql = sql[0 .. $ - 1];
247         if (_pageable)
248         {
249             sql ~= " limit " ~ to!string(_pageable.getPageSize());
250             auto offset = _pageable.getOffset();
251             if (offset > 0)
252                 sql ~= " offset " ~ to!string(offset);
253         }
254         else if (_limit != -1)
255         {
256             sql ~= " limit " ~ to!string(_limit);
257             if (_offset != -1)
258                 sql ~= " offset " ~ to!string(_offset);
259         }
260         return sql;
261     }
262 
263     int exec()
264     {
265         auto sql = getExecSql();
266         version (WITH_HUNT_TRACE)
267         {
268             beginTrace("EqlQuery exec");
269             scope (exit)
270             {
271                 _tags["sql"] = sql;
272                 endTrace();
273             }
274         }
275         
276         Statement stmt = _manager.getSession().prepare(sql);
277         string autoIncrementKey = _entityInfo.autoIncrementKey;
278         int r = stmt.execute(autoIncrementKey);
279 
280         _lastInsertId = stmt.lastInsertId();
281         _affectRows = stmt.affectedRows();
282 
283         //TODO update 时 返回的row line count 为 0
284         return r;
285     }
286 
287     public int lastInsertId()
288     {
289         return _lastInsertId;
290     }
291 
292     public int affectedRows()
293     {
294         return _affectRows;
295     }
296 
297     public ResultObj getSingleResult()
298     {
299         Object[] ret = _getResultList();
300         if (ret.length == 0)
301             return null;
302         return cast(ResultObj)(ret[0]);
303     }
304 
305     public ResultObj[] getResultList()
306     {
307         Object[] ret = _getResultList();
308         if (ret.length == 0)
309         {
310             return null;
311         }
312         return cast(ResultObj[]) ret;
313     }
314 
315     public Page!ResultObj getPageResult()
316     {
317         if (_pageable) {
318             _countEql = PagerUtils.count(_eqlParser.getNativeSql, _eqlParser.getDBType());
319             version(HUNT_SQL_DEBUG) info(_countEql);
320         } else {
321             throw new Exception("please use 'createPageQuery'");
322         }
323 
324         auto res = getResultList();
325         return new Page!ResultObj(res, _pageable, count(_countEql));
326     }
327 
328     private Object[] _getResultList()
329     {
330         auto sql = getExecSql();
331         version (WITH_HUNT_TRACE)
332         {
333             beginTrace("EqlQuery _getResultList");
334             scope (exit)
335             {
336                 _tags["sql"] = sql;
337                 endTrace();
338             }
339         }
340 
341         Object[] ret;
342         long count = -1;
343         Statement stmt = _manager.getSession().prepare(sql);
344         RowSet res = stmt.query();
345         if(res is null) {
346             warning("The result of query is empty");
347             return null;
348         }
349 
350         Row[] rows;
351         foreach (value; res)
352         {
353             rows ~= value;
354         }
355 
356         foreach (size_t k, Row v; rows)
357         {
358             try
359             {
360                 version(HUNT_ENTITY_DEBUG) {
361                     warningf("Deserializing row %d", k);
362                 }
363 
364                 // FIXME: Needing refactor or cleanup -@zhangxueping at 2020-11-03T10:21:26+08:00
365                 // The parameter of canInit can be set
366                 ResultObj t = _resultDes.deSerialize!(ResultObj, true)(rows, count, cast(int) k, null);
367                 if (t is null)
368                 {
369                     if (count != -1)
370                     {
371                         ret ~= new Long(count);
372                     }
373                     else
374                     {
375                         throw new EntityException("empty row data");
376                     }
377                 } 
378                 ret ~= t;
379             }
380             catch (Exception e)
381             {
382                 version(HUNT_SQL_DEBUG) warning(e);
383                 else version(HUNT_DEBUG) warning(e.msg);
384                 throw new EntityException(e.msg);
385             }
386 
387         }
388         return ret;
389     }
390 
391     public RowSet getNativeResult()
392     {
393         auto sql = getExecSql();
394         version (WITH_HUNT_TRACE)
395         {
396             beginTrace("EqlQuery getNativeResult");
397             scope (exit)
398             {
399                 _tags["sql"] = sql;
400                 endTrace();
401             }
402         }
403 
404         auto stmt = _manager.getSession().prepare(sql);
405         return stmt.query();
406     }
407 
408     private long count(string sql)
409     {
410         version (WITH_HUNT_TRACE)
411         {
412             beginTrace("EqlQuery count");
413             scope (exit)
414             {
415                 _tags["sql"] = sql;
416                 endTrace();
417             }
418         }
419         long total = 0;
420         auto stmt = _manager.getSession().prepare(sql);
421         RowSet res = stmt.query();
422         foreach (Row row; res)
423         {
424             Variant v = row.getValue(0);
425             total = to!int(v.toString());
426         }
427         return total;
428     }
429 }