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.EntityFieldManyToMany;
13 
14 import hunt.entity;
15 import hunt.entity.EntityExpression;
16 import hunt.Exceptions;
17 
18 import hunt.logging;
19 
20 import std.format;
21 import std.variant;
22 
23 
24 class EntityFieldManyToMany(T : Object, F : Object = T,string MAPPEDBY = "") : EntityFieldObject!(T,F) {
25 
26 
27     private ManyToMany _mode;
28     private string _primaryKey;
29     private string _findString;
30     private T[][string] _decodeCache;
31     private EntityManager _em;
32 
33 
34     this(EntityManager manager, string fieldName, string primaryKey, string tableName, ManyToMany mode, F owner, bool isMainMapped ) {
35         // logDebug("ManyToMany(%s,%s) not main mapped ( %s , %s , %s , %s ,%s )".format(T.stringof,F.stringof,fieldName,primaryKey,tableName,mode,isMainMapped));
36         _em = manager;
37         super(manager, fieldName, "", tableName, null, owner);
38         _isMainMapped = isMainMapped;
39         init(primaryKey, mode, owner);
40     }
41 
42     this(EntityManager manager, string fieldName, string primaryKey, string tableName, ManyToMany mode, F owner, 
43             bool isMainMapped ,JoinTable joinTable , JoinColumn jc  ,InverseJoinColumn ijc ) {
44         // logDebug("ManyToMany(%s,%s) main mapped( %s , %s , %s , %s ,%s , %s , %s , %s )".format(T.stringof,F.stringof,fieldName,primaryKey,tableName,mode,isMainMapped,joinTable,jc,ijc));
45         _em = manager;
46         super(manager, fieldName, "", tableName, null, owner);
47         _isMainMapped = isMainMapped;
48         _joinColumn = jc.name;
49         _inverseJoinColumn = ijc.name;
50         _joinTable = joinTable.name;
51         init(primaryKey, mode, owner);
52     }
53 
54     private void init(string primaryKey,  ManyToMany mode, F owner) {
55         _mode = mode;       
56         _enableJoin = _mode.fetch == FetchType.EAGER;    
57         _primaryKey = primaryKey;
58         static if(MAPPEDBY != "")
59         {
60             if(!_isMainMapped )
61             {
62                 _inverseJoinColumn =  hunt.entity.utils.Common.getInverseJoinColumn!(T,MAPPEDBY).name;
63                 _joinColumn =   hunt.entity.utils.Common.getJoinColumn!(T,MAPPEDBY).name;
64                 _joinTable =  _em.getPrefix() ~ hunt.entity.utils.Common.getJoinTableName!(T,MAPPEDBY);
65             }
66         }
67         
68         // logDebug("----(%s , %s ,%s )".format(_joinColumn,_inverseJoinColumn,_joinTable));
69         
70         initJoinData();
71         initFindStr();
72     }
73 
74     private void initJoinData() {
75         _joinSqlData = new JoinSqlBuild(); 
76         _joinSqlData.tableName = _joinTable;
77         if(_isMainMapped)
78             _joinSqlData.joinWhere = _entityInfo.getTableName() ~ "." ~ _entityInfo.getPrimaryKeyString ~ " = " ~ _joinTable ~ "." ~ _inverseJoinColumn;
79         else
80             _joinSqlData.joinWhere = _entityInfo.getTableName() ~ "." ~ _entityInfo.getPrimaryKeyString ~ " = " ~ _joinTable ~ "." ~ _joinColumn;
81         _joinSqlData.joinType = JoinType.LEFT;
82         // foreach(value; _entityInfo.getFields()) {
83         //     _joinSqlData.columnNames ~= value.getSelectColumn();
84         // }
85         // logDebug("many to many join sql : %s ".format(_joinSqlData));
86     }
87 
88     override public string getSelectColumn() {
89         return "";
90     }
91 
92 
93     private void initFindStr() {
94         _findString = "SELECT ";
95         string[] el;
96         foreach(k,v; _entityInfo.getFields()) {
97             if (v.getSelectColumn() != "")
98                 el ~= " "~v.getSelectColumn();
99         }
100         foreach(k,v; el) {
101             _findString ~= v;
102             if (k != el.length -1)
103                 _findString ~= ",";
104         }
105         _findString ~= " FROM "~_joinTable~" WHERE " ~ _joinTable ~"."~_joinColumn~" = ";
106     }
107 
108     public string getPrimaryKey() {return _primaryKey;}
109     public ManyToMany getMode() {return _mode;}
110 
111     override FetchType fetchType() {
112         return _mode.fetch;
113     }
114 
115     public T[] deSerialize(Row[] rows, int startIndex, bool isFromManyToOne) {
116         T[] ret;
117         if (_mode.fetch == FetchType.LAZY)
118             return ret;
119         // logDebug("-----do do do ---");
120         T singleRet;
121         long count = -1;
122         if (!isFromManyToOne) {
123             foreach(value; rows) {
124                 singleRet = _entityInfo.deSerialize([value], count);
125                 if (singleRet)
126                     ret ~= Common.sampleCopy(singleRet);
127             }
128         }
129         else {
130             string joinValue = getJoinKeyValue(rows[startIndex]);
131             if (joinValue == "")
132                 return ret;
133             T[] rets = getValueByJoinKeyValue(joinValue);
134             foreach(value; rets) {
135                 ret ~= Common.sampleCopy(value);
136             }
137         }
138         return ret;
139     }
140 
141     private string getJoinKeyValue(Row row) {
142         string name = EntityExpression.getColumnAsName(_primaryKey, getTableName());
143         Variant v = row.getValue(name);
144         if(!v.hasValue()) {
145             version(HUNT_DEBUG) warningf("Can't find value for %s", name);
146             return null;
147         }
148         
149         string value = v.toString();
150         version(HUNT_ENTITY_DEBUG) tracef("A column: %s = %s", name, value);
151         return value;                
152     }
153 
154     private T[] getValueByJoinKeyValue(string key) {
155         if (key in _decodeCache)
156             return _decodeCache[key];
157 
158         T[] ret;
159         T singleRet;
160         auto stmt = _manager.getSession().prepare(_findString~key);
161 		auto res = stmt.query();
162         long count = -1;
163         foreach(value; res) {
164             singleRet = _entityInfo.deSerialize([value], count);
165             if (singleRet)
166                 ret ~=  singleRet;
167         }
168         _decodeCache[key] = ret;
169         return _decodeCache[key];
170     }
171 
172     public void setMode(ManyToMany mode) {
173         _mode = mode;
174         _enableJoin = _mode.fetch == FetchType.EAGER;    
175     }
176 
177     override LazyData getLazyData(Row row) {
178         logDebug("--- MappedBy : %s , row : %s ".format(_mode.mappedBy, row));
179         string name = EntityExpression.getColumnAsName(_primaryKey, getTableName());
180         Variant v = row.getValue(name);
181         if(!v.hasValue()) {
182             warningf("Can't find value for %s", name);
183             return null;
184         }
185         
186         string value = v.toString();
187         version(HUNT_ENTITY_DEBUG) tracef("A column: %s = %s", name, value);
188         LazyData ret = new LazyData(_mode.mappedBy, value);
189         return ret;
190     }
191 
192 
193 
194 }