1 /*
2  * Copyright 2015-2018 HuntLabs.cn
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 module hunt.sql.visitor.ParameterizedOutputVisitorUtils;
17 
18 import hunt.collection;
19 
20 import hunt.sql.SQLUtils;
21 import hunt.sql.ast;
22 import hunt.sql.ast.statement;
23 // import hunt.sql.dialect.db2.visitor.DB2OutputVisitor;
24 import hunt.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
25 import hunt.sql.dialect.mysql.visitor.MySqlASTVisitor;
26 import hunt.sql.dialect.mysql.visitor.MySqlOutputVisitor;
27 // import hunt.sql.dialect.oracle.visitor.OracleParameterizedOutputVisitor;
28 // import hunt.sql.dialect.phoenix.visitor.PhoenixOutputVisitor;
29 import hunt.sql.dialect.postgresql.visitor.PGOutputVisitor;
30 // import hunt.sql.dialect.sqlserver.visitor.SQLServerOutputVisitor;
31 import hunt.sql.parser;
32 import hunt.sql.util.FnvHash;
33 import hunt.sql.util.DBType;
34 import hunt.sql.visitor.ParameterizedVisitor;
35 import hunt.sql.visitor.VisitorFeature;
36 import hunt.String;
37 import hunt.sql.visitor.SQLASTOutputVisitor;
38 import hunt.util.Appendable;
39 import hunt.util.Common;
40 import hunt.text;
41 
42 public class ParameterizedOutputVisitorUtils {
43     private  static SQLParserFeature[] defaultFeatures = [
44             SQLParserFeature.EnableSQLBinaryOpExprGroup,
45             SQLParserFeature.UseInsertColumnsCache,
46             SQLParserFeature.OptimizedForParameterized
47     ];
48 
49     private  static SQLParserFeature[] defaultFeatures2 = [
50             SQLParserFeature.EnableSQLBinaryOpExprGroup,
51             SQLParserFeature.UseInsertColumnsCache,
52             SQLParserFeature.OptimizedForParameterized,
53             SQLParserFeature.OptimizedForForParameterizedSkipValue,
54     ];
55 
56     public static string parameterize(string sql, string dbType) {
57         return parameterize(sql, dbType, null, null,null);
58     }
59 
60     public static string parameterize(string sql
61             , string dbType
62             , SQLSelectListCache selectListCache) {
63         return parameterize(sql, dbType, selectListCache, null);
64     }
65 
66     public static string parameterize(string sql
67             , string dbType
68             , List!(Object) outParameters) {
69         return parameterize(sql, dbType, null, outParameters);
70     }
71 
72 
73     private static void configVisitorFeatures(ParameterizedVisitor visitor, VisitorFeature[] features...) {
74         if(features !is null) {
75             for (int i = 0; i < features.length; i++) {
76                 visitor.config(features[i], true);
77             }
78         }
79     }
80 
81     public static string parameterize(string sql
82             , string dbType
83             , List!(Object) outParameters, VisitorFeature[] features...) {
84         return parameterize(sql, dbType, null, outParameters, features);
85     }
86 
87     public static string parameterize(string sql
88             , string dbType
89             , SQLSelectListCache selectListCache, List!(Object) outParameters, VisitorFeature[] visitorFeatures...) {
90 
91          SQLParserFeature[] features = outParameters is null
92                 ? defaultFeatures2
93                 : defaultFeatures;
94 
95         SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
96 
97         if (selectListCache !is null) {
98             parser.setSelectListCache(selectListCache);
99         }
100 
101         List!(SQLStatement) statementList = parser.parseStatementList();
102         if (statementList.size() == 0) {
103             return sql;
104         }
105 
106         StringBuilder out_p = new StringBuilder(sql.length);
107         ParameterizedVisitor visitor = createParameterizedOutputVisitor(out_p, dbType);
108         if (outParameters !is null) {
109             visitor.setOutputParameters(outParameters);
110         }
111         configVisitorFeatures(visitor, visitorFeatures);
112 
113         for (int i = 0; i < statementList.size(); i++) {
114             SQLStatement stmt = statementList.get(i);
115 
116             if (i > 0) {
117                 SQLStatement preStmt = statementList.get(i - 1);
118 
119                 if (typeid(preStmt) == typeid(stmt)) {
120                     StringBuilder buf = new StringBuilder();
121                     ParameterizedVisitor v1 = createParameterizedOutputVisitor(buf, dbType);
122                     preStmt.accept(v1);
123                     if (out_p.toString() == (buf.toString())) {
124                         continue;
125                     }
126                 }
127 
128                 if (!preStmt.isAfterSemi()) {
129                     out_p.append(";\n");
130                 } else {
131                     out_p.append('\n');
132                 }
133             }
134 
135             if (stmt.hasBeforeComment()) {
136                 stmt.getBeforeCommentsDirect().clear();
137             }
138 
139             auto stmtClass = typeid(stmt);
140             if (stmtClass == typeid(SQLSelectStatement)) { // only for performance
141                 SQLSelectStatement selectStatement = cast(SQLSelectStatement) stmt;
142                 visitor.visit(selectStatement);
143                 visitor.postVisit(selectStatement);
144             } else {
145                 stmt.accept(visitor);
146             }
147         }
148 
149         if (visitor.getReplaceCount() == 0
150                 && parser.getLexer().getCommentCount() == 0 && charAt(sql, 0) != '/') {
151             return sql;
152         }
153 
154         return out_p.toString();
155     }
156 
157     public static long parameterizeHash(string sql
158             , string dbType
159             , List!(Object) outParameters) {
160         return parameterizeHash(sql, dbType, null, outParameters, null);
161     }
162 
163     public static long parameterizeHash(string sql
164             , string dbType
165             , SQLSelectListCache selectListCache
166             , List!(Object) outParameters, VisitorFeature[] visitorFeatures...) {
167 
168          SQLParserFeature[] features = outParameters is null
169                 ? defaultFeatures2
170                 : defaultFeatures;
171 
172         SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
173 
174         if (selectListCache !is null) {
175             parser.setSelectListCache(selectListCache);
176         }
177 
178         List!(SQLStatement) statementList = parser.parseStatementList();
179          int stmtSize = statementList.size();
180         if (stmtSize == 0) {
181             return 0L;
182         }
183 
184         StringBuilder out_p = new StringBuilder(sql.length);
185         ParameterizedVisitor visitor = createParameterizedOutputVisitor(out_p, dbType);
186         if (outParameters !is null) {
187             visitor.setOutputParameters(outParameters);
188         }
189         configVisitorFeatures(visitor, visitorFeatures);
190 
191         if (stmtSize == 1) {
192             SQLStatement stmt = statementList.get(0);
193             if (typeid(stmt) == typeid(SQLSelectStatement)) {
194                 SQLSelectStatement selectStmt = cast(SQLSelectStatement) stmt;
195 
196                 if (selectListCache !is null) {
197                     SQLSelectQueryBlock queryBlock = selectStmt.getSelect().getQueryBlock();
198                     if (queryBlock !is null) {
199                         string cachedSelectList = queryBlock.getCachedSelectList();
200                         long cachedSelectListHash = queryBlock.getCachedSelectListHash();
201                         if (cachedSelectList !is null) {
202                             visitor.config(VisitorFeature.OutputSkipSelectListCacheString, true);
203                         }
204 
205                         visitor.visit(selectStmt);
206                         return FnvHash.fnv1a_64_lower(cachedSelectListHash, out_p);
207                     }
208                 }
209 
210                 visitor.visit(selectStmt);
211             } else if (typeid(stmt) == typeid(MySqlInsertStatement)) {
212                 MySqlInsertStatement insertStmt = cast(MySqlInsertStatement) stmt;
213                 string columnsString = insertStmt.getColumnsString();
214                 if (columnsString !is null) {
215                     long columnsStringHash = insertStmt.getColumnsStringHash();
216                     visitor.config(VisitorFeature.OutputSkipInsertColumnsString, true);
217 
218                     (cast(MySqlASTVisitor) visitor).visit(insertStmt);
219                     return FnvHash.fnv1a_64_lower(columnsStringHash, out_p);
220                 }
221             } else {
222                 stmt.accept(visitor);
223             }
224 
225             return FnvHash.fnv1a_64_lower(out_p);
226         }
227 
228         for (int i = 0; i < statementList.size(); i++) {
229             if (i > 0) {
230                 out_p.append(";\n");
231             }
232             SQLStatement stmt = statementList.get(i);
233 
234             if (stmt.hasBeforeComment()) {
235                 stmt.getBeforeCommentsDirect().clear();
236             }
237 
238             auto stmtClass = typeid(stmt);
239             if (stmtClass == typeid(SQLSelectStatement)) { // only for performance
240                 SQLSelectStatement selectStatement = cast(SQLSelectStatement) stmt;
241                 visitor.visit(selectStatement);
242                 visitor.postVisit(selectStatement);
243             } else {
244                 stmt.accept(visitor);
245             }
246         }
247 
248         return FnvHash.fnv1a_64_lower(out_p);
249     }
250 
251     public static string parameterize(List!(SQLStatement) statementList, string dbType) {
252         StringBuilder out_p = new StringBuilder();
253         ParameterizedVisitor visitor = createParameterizedOutputVisitor(out_p, dbType);
254 
255         for (int i = 0; i < statementList.size(); i++) {
256             if (i > 0) {
257                 out_p.append(";\n");
258             }
259             SQLStatement stmt = statementList.get(i);
260 
261             if (stmt.hasBeforeComment()) {
262                 stmt.getBeforeCommentsDirect().clear();
263             }
264             stmt.accept(visitor);
265         }
266 
267         return out_p.toString();
268     }
269 
270     public static ParameterizedVisitor createParameterizedOutputVisitor(Appendable out_p, string dbType) {
271         // if (DBType.ORACLE.opEquals(dbType) || DBType.ALI_ORACLE.opEquals(dbType)) {
272         //     return new OracleParameterizedOutputVisitor(out_p);
273         // }
274 
275         if (DBType.MYSQL.opEquals(dbType)
276             || DBType.MARIADB.opEquals(dbType)
277             || DBType.H2.opEquals(dbType)) {
278             return new MySqlOutputVisitor(out_p, true);
279         }
280 
281         if (DBType.POSTGRESQL.opEquals(dbType)
282                 || DBType.ENTERPRISEDB.opEquals(dbType)) {
283             return new PGOutputVisitor(out_p, true);
284         }
285 
286         // if (DBType.SQL_SERVER.opEquals(dbType) || DBType.JTDS.opEquals(dbType)) {
287         //     return new SQLServerOutputVisitor(out_p, true);
288         // }
289 
290         // if (DBType.DB2.opEquals(dbType)) {
291         //     return new DB2OutputVisitor(out_p, true);
292         // }
293 
294         // if (DBType.PHOENIX.opEquals(dbType)) {
295         //     return new PhoenixOutputVisitor(out_p, true);
296         // }
297 
298         if (DBType.ELASTIC_SEARCH.opEquals(dbType)) {
299             return new MySqlOutputVisitor(out_p, true);
300         }
301 
302         return new SQLASTOutputVisitor(out_p, true);
303     }
304 
305     public static string restore(string sql, string dbType, List!(Object) parameters) {
306         List!(SQLStatement) stmtList = SQLUtils.parseStatements(sql, dbType);
307 
308         StringBuilder out_p = new StringBuilder();
309         SQLASTOutputVisitor visitor = SQLUtils.createOutputVisitor(out_p, dbType);
310         visitor.setInputParameters(parameters);
311 
312         foreach(SQLStatement stmt ; stmtList) {
313             stmt.accept(visitor);
314         }
315 
316         return out_p.toString();
317     }
318 }