1 /*
2  * Database - Database abstraction layer for D programing language.
3  *
4  * Copyright (C) 2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.database.Statement;
13 
14 import hunt.database.Database;
15 import hunt.database.DatabaseOption;
16 import hunt.database.base;
17 import hunt.database.query.Common;
18 
19 import hunt.logging;
20 import hunt.String;
21 import hunt.Integer;
22 import hunt.Long;
23 import hunt.Double;
24 import hunt.Float;
25 import hunt.Byte;
26 import hunt.Short;
27 import hunt.Nullable;
28 
29 import std.stdio;
30 import std.regex;
31 import std.variant;
32 
33 /**
34  * 
35  * See_Also:
36  *    https://www.codemeright.com/blog/post/named-parameterized-query-java
37  *    https://www.javaworld.com/article/2077706/named-parameters-for-preparedstatement.html
38  *    https://github.com/marcosemiao/jdbc-named-parameters/tree/master/src/main/java/fr/ms/sql
39  */
40 class Statement
41 {
42     private SqlConnection _sqlConn = null;
43     private string _sql;
44     private bool _isUsed = false;
45     private int _lastInsertId;
46     private int _affectRows;
47     private RowSet _rs;
48     private Object[string] _parameters;
49 
50     private DatabaseOption _options;
51 
52     this(SqlConnection db, DatabaseOption options)
53     {
54         _sqlConn = db;
55         _options = options;
56     }
57 
58     this(SqlConnection db, string sql, DatabaseOption options)
59     {
60         _sqlConn = db;
61         _options = options;
62         prepare(sql);
63     }
64 
65     void prepare(string sql)
66     {
67         assert(sql.length);
68         this._sql = sql;
69         _needReset = true;
70 
71     }
72 
73     private bool _needReset = false;
74 
75     void setParameter(R)(string key, R param)
76     {
77         static if (is(R == int) || is(R == uint))
78         {
79             _parameters[key] = new Integer(param);
80         }
81         else static if (is(R == string) || is(R == char) || is(R == byte[]))
82         {
83             _parameters[key] = new String(param);
84         }
85         else static if (is(R == bool))
86         {
87             _parameters[key] = new Boolean(param);
88         }
89         else static if (is(R == double))
90         {
91             _parameters[key] = new Double(param);
92         }
93         else static if (is(R == float))
94         {
95             _parameters[key] = new Float(param);
96         }
97         else static if (is(R == short) || is(R == ushort))
98         {
99             _parameters[key] = new Short(param);
100         }
101         else static if (is(R == long) || is(R == ulong))
102         {
103             _parameters[key] = new Long(param);
104         }
105         else static if (is(R == byte) || is(R == ubyte))
106         {
107             _parameters[key] = new Byte(param);
108         }
109         else static if (is(R == class))
110         {
111             _parameters[key] = param;
112         }
113         else
114         {
115             throw new Exception("IllegalArgument not support : " ~ R.stringof);
116         }
117         _needReset = true;
118     }
119 
120     // string sql()
121     // {
122     //     auto conn = _sqlConn.getConnection();
123     //     scope (exit)
124     //         _sqlConn.relaseConnection(conn);
125     //     return sql(conn);
126     // }
127 
128     private string sql(SqlConnection conn)
129     {
130         if (!_needReset)
131             return _str;
132 
133         string str = _sql;
134 
135         foreach (string k, Object v; _parameters)
136         {
137             auto re = regex(r":" ~ k ~ r"([^\w]*)", "g");
138             if ((cast(String) v !is null) || (cast(Nullable!string) v !is null))
139             {
140                 str = str.replaceAll(re, "'" ~ v.toString() ~ "'" ~ "$1");
141                 
142         //         if (_sqlConn.getOption().isPgsql() || _sqlConn.getOption().isMysql()) {
143         //             // str = str.replaceAll(re, conn.escapeLiteral(v.toString()) ~ "$1");
144         //             // str = str.replaceAll(re, v.toString() ~ "$1");
145         // // warning(str ~ "      " ~ v.toString() ~ "$1");
146         //         // } else if (_sqlConn.getOption().isMysql()) {
147         //             // str = str.replaceAll(re, "'" ~ conn.escape(v.toString()) ~ "'" ~ "$1");
148         //             str = str.replaceAll(re, "'" ~ v.toString() ~ "'" ~ "$1");
149         //         }
150         //         else
151         //         {
152         //             str = str.replaceAll(re, quoteSqlString(v.toString()) ~ "$1");
153         //         }
154             }
155             else
156             {
157                 str = str.replaceAll(re, v.toString() ~ "$1");
158             }
159         }
160 
161         _needReset = false;
162         _str = str;
163         return str;
164     }
165 
166     private string _str;
167 
168     int execute()
169     {
170         string execSql = sql(_sqlConn);
171 
172         version (HUNT_SQL_DEBUG)
173             logDebug(execSql);
174 
175         _rs = _sqlConn.query(execSql);
176         _lastInsertId = 0;
177 
178         if(_options.isPgsql()) {
179             Row row = _rs.lastRow();
180             if(row !is null) {
181                 _lastInsertId = row.getInteger(0);
182             }
183         } else if (_options.isMysql()) {
184             import hunt.database.driver.mysql.MySQLClient;
185             Variant value2 = _rs.property(MySQLClient.LAST_INSERTED_ID);
186             if(value2.type != typeid(int)) {
187                 warning("Not expected type: ", value2.type);
188             } else {
189                 _lastInsertId = value2.get!int();
190             }
191         } 
192 
193         // import hunt.database.driver.mysql.MySQLClient;
194         // Variant value2 = _rs.property(MySQLClient.LAST_INSERTED_ID);
195         // if(value2.type != typeid(int)) {
196         //     version(HUNT_DEBUG) warning("Not expected type: ", value2.type);
197         //     _lastInsertId = 0;
198         // } else {
199         //     _lastInsertId = value2.get!int();
200         // }        
201 
202         _affectRows = _rs.rowCount();
203         return _affectRows;
204     }
205 
206     
207     int execute(string lastIdName)
208     {
209         string execSql = sql(_sqlConn);
210         version (HUNT_SQL_DEBUG) logDebug(execSql);
211 
212         _rs = _sqlConn.query(execSql);
213         _lastInsertId = 0;
214 
215         if(_options.isPgsql()) {
216             Row row = _rs.lastRow();
217             if(row !is null) {
218                 _lastInsertId = row.getInteger(lastIdName);
219             }
220         } else if (_options.isMysql()) {
221             import hunt.database.driver.mysql.MySQLClient;
222             Variant value2 = _rs.property(MySQLClient.LAST_INSERTED_ID);
223             if(value2.type != typeid(int)) {
224                 warning("Not expected type: ", value2.type);
225             } else {
226                 _lastInsertId = value2.get!int();
227             }
228         }
229 
230         _affectRows = _rs.rowCount();
231         return _affectRows;
232     }
233 
234     int lastInsertId()
235     {
236         return _lastInsertId;
237     }
238 
239     int affectedRows()
240     {
241         return _affectRows;
242     }
243 
244 
245     int count()
246     {
247         Row res = fetch();
248         return res.getInteger(0);
249     }
250 
251     Row fetch()
252     {
253         if (!_rs)
254             _rs = query();
255 
256         foreach(Row r; _rs) {
257             return r;
258         }
259 
260         throw new DatabaseException("RowSet is empty");
261     }
262 
263     RowSet query()
264     {
265         string execSql = sql(null);
266         _rs = _sqlConn.query(execSql);
267         return _rs;
268     }
269 
270     // void close()
271     // {
272     //     version (HUNT_DEBUG)
273     //         info("statement closed");
274     // }
275 
276     // private void isUsed()
277     // {
278     //     // scope (exit)
279     //     //     _isUsed = true;
280     //     if (_isUsed)
281     //         throw new DatabaseException("statement was used");
282     //     _isUsed = true;
283     // }
284 
285 }