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 }