1 module hunt.database.base.impl.NamedQueryDesc;
2 
3 import hunt.database.base.Exceptions;
4 
5 import std.conv;
6 import std.string;
7 import std.array;
8 import std.conv;
9 
10 /**
11  * 
12  */
13 abstract class AbstractNamedQueryDesc {
14 
15     protected int[][string] indexSet;
16 
17 	protected string namedSql;
18 
19 	protected string sql;
20 
21 	protected int _size;
22 
23     this(string sql) {
24 		this.namedSql = sql;
25 		this.sql = sql;
26     }
27 
28 	int[][string] getIndexSet() {
29 		return indexSet;
30 	}
31 
32 	string getNamedSql() {
33 		return namedSql;
34 	}
35 
36 	string getSql() {
37 		return sql;
38 	}
39 
40 	int getSize() {
41 		return _size;
42 	}
43 }
44 
45 /**
46  * See_Also:
47  *  https://github.com/bnewport/Samples/blob/master/wxsutils/src/main/java/com/devwebsphere/jdbc/loader/NamedParameterStatement.java
48  */
49 class NamedQueryDesc(string symbol, bool hasNumber) : AbstractNamedQueryDesc {
50 
51 	/**
52 	 * Set of characters that qualify as comment or quotes starting characters.
53 	 */
54 	private enum string[] START_SKIP = ["'", "\"", "--", "/*"];
55 
56 	/**
57 	 * Set of characters that at are the corresponding comment or quotes ending
58 	 * characters.
59 	 */
60 	private enum string[] STOP_SKIP = ["'", "\"", "\n", "*/"];
61 
62     
63 	this(string namedSql) {
64         super(namedSql);
65         parse(namedSql);
66     }
67 
68 	private void parse(string namedSql) {
69 
70 		string statement = namedSql;
71 		int nbParameter = 1;
72 
73 		size_t i = 0;
74 		while (i < statement.length) {
75 			size_t skipToPosition = i;
76 			while (i < statement.length) {
77 				skipToPosition = skipCommentsAndQuotes(statement, i);
78 				if (i == skipToPosition) {
79 					break;
80 				} else {
81 					i = skipToPosition;
82 				}
83 			}
84 			if (i >= statement.length) {
85 				break;
86 			}
87 			char c = statement[i];
88 
89 			if (c == ':' || c == '&') {
90 				size_t j = i + 1;
91 				if (j < statement.length && statement[j] == ':' && c == ':') {
92 					// Postgres-style "::" casting operator should be skipped
93 					i = i + 2;
94 					continue;
95 				}
96 
97 				if (j < statement.length && c == ':' && statement[j] == '{') {
98 					// :{x} style parameter
99 					while (j < statement.length && !('}' == statement[j])) {
100 						j++;
101 						if (':' == statement[j] || '{' == statement[j]) {
102 							throw new DatabaseException("Parameter name contains invalid character '" ~ statement[j]
103 									~ "' at position " ~ i.to!string() ~ " in statement: " ~ namedSql);
104 						}
105 					}
106 					if (j >= statement.length) {
107 						throw new DatabaseException("Non-terminated named parameter declaration at position " ~ i.to!string()
108 								~ " in statement: " ~ namedSql);
109 					}
110 					if (j - i > 3) {
111 						string parameter = namedSql[i + 2 .. j];
112 
113                         if(hasNumber)
114 						    sql = sql.replaceFirst(":" ~ parameter, symbol ~ nbParameter.to!(string));
115                         else
116 						    sql = sql.replaceFirst(":" ~ parameter, symbol);
117                         indexSet[parameter] ~= nbParameter;
118 						nbParameter = nbParameter + 1;
119 					}
120 					j++;
121 				} else {
122 					while (j < statement.length && !isParameterSeparator(statement[j])) {
123 						j++;
124 					}
125 					if (j - i > 1) {
126 						string parameter = namedSql[i + 1 .. j];
127                         if(hasNumber)
128 						    sql = sql.replaceFirst(":" ~ parameter, symbol ~ nbParameter.to!(string));
129                         else
130 						    sql = sql.replaceFirst(":" ~ parameter, symbol);
131 
132                         indexSet[parameter] ~= nbParameter;
133 						nbParameter = nbParameter + 1;
134 					}
135 				}
136 				i = j - 1;
137 			}
138 			i++;
139 
140 			_size = nbParameter-1;
141 		}
142 	}
143 
144 	/**
145 	 * Skip over comments and quoted names present in an SQL statement
146 	 * 
147 	 * @param statement
148 	 *            character array containing SQL statement
149 	 * @param position
150 	 *            current position of statement
151 	 * @return next position to process after any comments or quotes are skipped
152 	 */
153 	private static size_t skipCommentsAndQuotes(string statement, size_t position) {
154 		for (size_t i = 0; i < START_SKIP.length; i++) {
155 			if (statement[position] == START_SKIP[i][0]) {
156 				bool match = true;
157 				for (size_t j = 1; j < START_SKIP[i].length; j++) {
158 					if (!(statement[position + j] == START_SKIP[i][j])) {
159 						match = false;
160 						break;
161 					}
162 				}
163 				if (match) {
164 					size_t offset = START_SKIP[i].length;
165 					for (size_t m = position + offset; m < statement.length; m++) {
166 						if (statement[m] == STOP_SKIP[i][0]) {
167 							bool endMatch = true;
168 							size_t endPos = m;
169 							for (size_t n = 1; n < STOP_SKIP[i].length; n++) {
170 								if (m + n >= statement.length) {
171 									// last comment not closed properly
172 									return statement.length;
173 								}
174 								if (!(statement[m + n] == STOP_SKIP[i][n])) {
175 									endMatch = false;
176 									break;
177 								}
178 								endPos = m + n;
179 							}
180 							if (endMatch) {
181 								// found character sequence ending comment or
182 								// quote
183 								return endPos + 1;
184 							}
185 						}
186 					}
187 					// character sequence ending comment or quote not found
188 					return statement.length;
189 				}
190 
191 			}
192 		}
193 		return position;
194 	}
195 
196 	private enum char[] PARAMETER_SEPARATORS = ['"', '\'', ':', '&', ',', ';', '(', ')', '|', '=',
197 			'+', '-', '*', '%', '/', '\\', '<', '>', '^'];
198 
199 	private static bool isParameterSeparator(char c) {
200         import std.ascii;
201 		if (isWhite(c)) {
202 			return true;
203 		}
204 		foreach (char separator; PARAMETER_SEPARATORS) {
205 			if (c == separator) {
206 				return true;
207 			}
208 		}
209 		return false;
210 	}
211 }