| | 100 | /+ |
|---|
| | 101 | class Statement : IPreparedStatement { |
|---|
| | 102 | /** |
|---|
| | 103 | * Make a new instance of Statement. |
|---|
| | 104 | * |
|---|
| | 105 | * Params: |
|---|
| | 106 | * database = The database connection to use. |
|---|
| | 107 | * sql = The SQL code to prepare. |
|---|
| | 108 | */ |
|---|
| | 109 | this (Database database, char[] sql) { |
|---|
| | 110 | this.database = database; |
|---|
| | 111 | this.sql = sql; |
|---|
| | 112 | } |
|---|
| | 113 | |
|---|
| | 114 | /** |
|---|
| | 115 | * Execute a SQL statement that returns no results. |
|---|
| | 116 | */ |
|---|
| | 117 | void execute () { |
|---|
| | 118 | database.execute(getSql()); |
|---|
| | 119 | } |
|---|
| | 120 | |
|---|
| | 121 | uint getParamCount(); |
|---|
| | 122 | FieldInfo[] getResultMetadata(); |
|---|
| | 123 | void setParamTypes(BindType[] paramTypes); |
|---|
| | 124 | void setResultTypes(BindType[] resTypes); |
|---|
| | 125 | void execute(); |
|---|
| | 126 | void execute(void*[] bind); |
|---|
| | 127 | bool fetch(void*[] bind); |
|---|
| | 128 | void prefetchAll(); |
|---|
| | 129 | void reset(); |
|---|
| | 130 | ulong getLastInsertID(); |
|---|
| | 131 | char[] getLastErrorMsg(); |
|---|
| | 132 | |
|---|
| | 133 | /** |
|---|
| | 134 | * Query the database. |
|---|
| | 135 | * |
|---|
| | 136 | * Returns: |
|---|
| | 137 | * A Result object with the queried information. |
|---|
| | 138 | */ |
|---|
| | 139 | Result query () { |
|---|
| | 140 | return database.query(getSql()); |
|---|
| | 141 | } |
|---|
| | 142 | |
|---|
| | 143 | private: |
|---|
| | 144 | Database database; |
|---|
| | 145 | char[] sql; |
|---|
| | 146 | char[][] binds; |
|---|
| | 147 | |
|---|
| | 148 | /** |
|---|
| | 149 | * Escape a SQL statement. |
|---|
| | 150 | * |
|---|
| | 151 | * Params: |
|---|
| | 152 | * string = An unescaped SQL statement. |
|---|
| | 153 | * |
|---|
| | 154 | * Returns: |
|---|
| | 155 | * The escaped form of string. |
|---|
| | 156 | */ |
|---|
| | 157 | char[] escape (char[] string) { |
|---|
| | 158 | if (database !is null) { |
|---|
| | 159 | return database.escape(string); |
|---|
| | 160 | } else { |
|---|
| | 161 | char[] result; |
|---|
| | 162 | size_t count = 0; |
|---|
| | 163 | |
|---|
| | 164 | // Maximum length needed if every char is to be quoted |
|---|
| | 165 | result.length = string.length * 2; |
|---|
| | 166 | |
|---|
| | 167 | for (size_t i = 0; i < string.length; i++) { |
|---|
| | 168 | switch (string[i]) { |
|---|
| | 169 | case '"': |
|---|
| | 170 | case '\'': |
|---|
| | 171 | case '\\': |
|---|
| | 172 | result[count++] = '\\'; |
|---|
| | 173 | break; |
|---|
| | 174 | default: |
|---|
| | 175 | break; |
|---|
| | 176 | } |
|---|
| | 177 | result[count++] = string[i]; |
|---|
| | 178 | } |
|---|
| | 179 | |
|---|
| | 180 | result.length = count; |
|---|
| | 181 | return result; |
|---|
| | 182 | } |
|---|
| | 183 | } |
|---|
| | 184 | |
|---|
| | 185 | /** |
|---|
| | 186 | * Replace every "?" in the current SQL statement with its bound value. |
|---|
| | 187 | * |
|---|
| | 188 | * Returns: |
|---|
| | 189 | * The current SQL statement with all occurences of "?" replaced. |
|---|
| | 190 | * |
|---|
| | 191 | * Todo: |
|---|
| | 192 | * Raise an exception if binds.length != count(sql, "?") |
|---|
| | 193 | */ |
|---|
| | 194 | char[] getSqlByQM () { |
|---|
| | 195 | char[] result; |
|---|
| | 196 | size_t i = 0, j = 0, count = 0; |
|---|
| | 197 | |
|---|
| | 198 | // binds.length is for the '', only 1 because we replace the ? too |
|---|
| | 199 | result.length = sql.length + binds.length; |
|---|
| | 200 | for (i = 0; i < binds.length; i++) { |
|---|
| | 201 | result.length = result.length + binds[i].length; |
|---|
| | 202 | } |
|---|
| | 203 | |
|---|
| | 204 | for (i = 0; i < sql.length; i++) { |
|---|
| | 205 | if (sql[i] == '?') { |
|---|
| | 206 | result[j++] = '\''; |
|---|
| | 207 | result[j .. j + binds[count].length] = binds[count]; |
|---|
| | 208 | j += binds[count++].length; |
|---|
| | 209 | result[j++] = '\''; |
|---|
| | 210 | } |
|---|
| | 211 | else { |
|---|
| | 212 | result[j++] = sql[i]; |
|---|
| | 213 | } |
|---|
| | 214 | } |
|---|
| | 215 | |
|---|
| | 216 | sql = result; |
|---|
| | 217 | return result; |
|---|
| | 218 | } |
|---|
| | 219 | |
|---|
| | 220 | /** |
|---|
| | 221 | * Replace every ":name:" in the current SQL statement with its bound value. |
|---|
| | 222 | * |
|---|
| | 223 | * Returns: |
|---|
| | 224 | * The current SQL statement with all occurences of ":name:" replaced. |
|---|
| | 225 | * |
|---|
| | 226 | * Todo: |
|---|
| | 227 | * Raise an exception if binds.length != (count(sql, ":") * 2) |
|---|
| | 228 | */ |
|---|
| | 229 | char[] getSqlByFN () { |
|---|
| | 230 | char[] result = sql; |
|---|
| | 231 | version (Phobos) { |
|---|
| | 232 | ptrdiff_t beginIndex = 0, endIndex = 0; |
|---|
| | 233 | while ((beginIndex = std.string.find(result, ":")) != -1 && (endIndex = std.string.find(result[beginIndex + 1 .. length], ":")) != -1) { |
|---|
| | 234 | result = result[0 .. beginIndex] ~ "'" ~ getBoundValue(result[beginIndex + 1.. beginIndex + endIndex + 1]) ~ "'" ~ result[beginIndex + endIndex + 2 .. length]; |
|---|
| | 235 | } |
|---|
| | 236 | } else { |
|---|
| | 237 | uint beginIndex = 0, endIndex = 0; |
|---|
| | 238 | while ((beginIndex = tango.text.Util.locate(result, ':')) != result.length && (endIndex = tango.text.Util.locate(result, ':', beginIndex + 1)) != result.length) { |
|---|
| | 239 | result = result[0 .. beginIndex] ~ "'" ~ getBoundValue(result[beginIndex + 1 .. endIndex]) ~ "'" ~ result[endIndex + 1 .. length]; |
|---|
| | 240 | } |
|---|
| | 241 | } |
|---|
| | 242 | return result; |
|---|
| | 243 | } |
|---|
| | 244 | |
|---|
| | 245 | /** |
|---|
| | 246 | * Replace all variables with their bound values. |
|---|
| | 247 | * |
|---|
| | 248 | * Returns: |
|---|
| | 249 | * The current SQL statement with all occurences of variables replaced. |
|---|
| | 250 | */ |
|---|
| | 251 | char[] getSql () { |
|---|
| | 252 | version (Phobos) { |
|---|
| | 253 | if (std.string.find(sql, "?") != -1) { |
|---|
| | 254 | return getSqlByQM(); |
|---|
| | 255 | } else if (std.string.find(sql, ":") != -1) { |
|---|
| | 256 | return getSqlByFN(); |
|---|
| | 257 | } else { |
|---|
| | 258 | return sql; |
|---|
| | 259 | } |
|---|
| | 260 | } else { |
|---|
| | 261 | if (tango.text.Util.contains(sql, '?')) { |
|---|
| | 262 | return getSqlByQM(); |
|---|
| | 263 | } else if (tango.text.Util.contains(sql, ':')) { |
|---|
| | 264 | return getSqlByFN(); |
|---|
| | 265 | } else { |
|---|
| | 266 | return sql; |
|---|
| | 267 | } |
|---|
| | 268 | } |
|---|
| | 269 | } |
|---|
| | 270 | |
|---|
| | 271 | /** |
|---|
| | 272 | * Get the value bound to a ":name:". |
|---|
| | 273 | * |
|---|
| | 274 | * Params: |
|---|
| | 275 | * fn = The ":name:" to return the bound value of. |
|---|
| | 276 | * |
|---|
| | 277 | * Returns: |
|---|
| | 278 | * The bound value of fn. |
|---|
| | 279 | * |
|---|
| | 280 | * Throws: |
|---|
| | 281 | * DBIException if fn is not bound |
|---|
| | 282 | */ |
|---|
| | 283 | char[] getBoundValue (char[] fn) { |
|---|
| | 284 | for (size_t index = 0; index < bindsFNs.length; index++) { |
|---|
| | 285 | if (bindsFNs[index] == fn) { |
|---|
| | 286 | return binds[index]; |
|---|
| | 287 | } |
|---|
| | 288 | } |
|---|
| | 289 | throw new DBIException(fn ~ " is not bound in the Statement."); |
|---|
| | 290 | } |
|---|
| | 291 | } |
|---|
| | 292 | |
|---|
| | 293 | debug(UnitTest) { |
|---|
| | 294 | unittest { |
|---|
| | 295 | version (Phobos) { |
|---|
| | 296 | void s1 (char[] s) { |
|---|
| | 297 | std.stdio.writefln("%s", s); |
|---|
| | 298 | } |
|---|
| | 299 | |
|---|
| | 300 | void s2 (char[] s) { |
|---|
| | 301 | std.stdio.writefln(" ...%s", s); |
|---|
| | 302 | } |
|---|
| | 303 | } else { |
|---|
| | 304 | void s1 (char[] s) { |
|---|
| | 305 | tango.io.Stdout.Stdout(s).newline(); |
|---|
| | 306 | } |
|---|
| | 307 | |
|---|
| | 308 | void s2 (char[] s) { |
|---|
| | 309 | tango.io.Stdout.Stdout(" ..." ~ s).newline(); |
|---|
| | 310 | } |
|---|
| | 311 | } |
|---|
| | 312 | |
|---|
| | 313 | s1("dbi.Statement:"); |
|---|
| | 314 | Statement stmt = new Statement(null, "SELECT * FROM people"); |
|---|
| | 315 | char[] resultingSql = "SELECT * FROM people WHERE id = '10' OR name LIKE 'John Mc\\'Donald'"; |
|---|
| | 316 | |
|---|
| | 317 | s2("escape"); |
|---|
| | 318 | assert (stmt.escape("John Mc'Donald") == "John Mc\\'Donald"); |
|---|
| | 319 | |
|---|
| | 320 | s2("simple sql"); |
|---|
| | 321 | stmt = new Statement(null, "SELECT * FROM people"); |
|---|
| | 322 | assert (stmt.getSql() == "SELECT * FROM people"); |
|---|
| | 323 | |
|---|
| | 324 | s2("bind by '?'"); |
|---|
| | 325 | stmt = new Statement(null, "SELECT * FROM people WHERE id = ? OR name LIKE ?"); |
|---|
| | 326 | stmt.bind(1, "10"); |
|---|
| | 327 | stmt.bind(2, "John Mc'Donald"); |
|---|
| | 328 | assert (stmt.getSql() == resultingSql); |
|---|
| | 329 | |
|---|
| | 330 | /+ |
|---|
| | 331 | s2("bind by '?' sent to getSql via variable arguments"); |
|---|
| | 332 | stmt = new Statement("SELECT * FROM people WHERE id = ? OR name LIKE ?"); |
|---|
| | 333 | assert (stmt.getSql("10", "John Mc'Donald") == resultingSql); |
|---|
| | 334 | +/ |
|---|
| | 335 | |
|---|
| | 336 | s2("bind by ':fieldname:'"); |
|---|
| | 337 | stmt = new Statement(null, "SELECT * FROM people WHERE id = :id: OR name LIKE :name:"); |
|---|
| | 338 | stmt.bind("id", "10"); |
|---|
| | 339 | stmt.bind("name", "John Mc'Donald"); |
|---|
| | 340 | assert (stmt.getBoundValue("name") == "John Mc\\'Donald"); |
|---|
| | 341 | assert (stmt.getSql() == resultingSql); |
|---|
| | 342 | } |
|---|
| | 343 | } |
|---|
| | 344 | +/ |
|---|