1 /++ 2 Use a DB via plain SQL statements (safe version). 3 4 Commands that are expected to return a result set - queries - have distinctive 5 methods that are enforced. That is it will be an error to call such a method 6 with an SQL command that does not produce a result set. So for commands like 7 SELECT, use the `query` functions. For other commands, like 8 INSERT/UPDATE/CREATE/etc, use `exec`. 9 10 This is the @safe version of mysql's command module, and as such uses the @safe 11 rows and result ranges, and the `MySQLVal` type. For the `Variant` unsafe 12 version, please import `mysql.unsafe.commands`. 13 14 $(SAFE_MIGRATION) 15 +/ 16 17 module mysql.safe.commands; 18 19 import std.conv; 20 import std.exception; 21 import std.range; 22 import std.typecons; 23 import std.variant; 24 25 import mysql.safe.connection; 26 import mysql.exceptions; 27 import mysql.safe.prepared; 28 import mysql.protocol.comms; 29 import mysql.protocol.constants; 30 import mysql.protocol.extra_types; 31 import mysql.protocol.packets; 32 import mysql.impl.result; 33 import mysql.types; 34 35 /// This feature is not yet implemented. It currently has no effect. 36 /+ 37 A struct to represent specializations of returned statement columns. 38 39 If you are executing a query that will include result columns that are large objects, 40 it may be expedient to deal with the data as it is received rather than first buffering 41 it to some sort of byte array. These two variables allow for this. If both are provided 42 then the corresponding column will be fed to the stipulated delegate in chunks of 43 `chunkSize`, with the possible exception of the last chunk, which may be smaller. 44 The bool argument `finished` will be set to true when the last chunk is set. 45 46 Be aware when specifying types for column specializations that for some reason the 47 field descriptions returned for a resultset have all of the types TINYTEXT, MEDIUMTEXT, 48 TEXT, LONGTEXT, TINYBLOB, MEDIUMBLOB, BLOB, and LONGBLOB lumped as type 0xfc 49 contrary to what it says in the protocol documentation. 50 +/ 51 struct ColumnSpecialization 52 { 53 size_t cIndex; // parameter number 0 - number of params-1 54 ushort type; 55 uint chunkSize; /// In bytes 56 void delegate(const(ubyte)[] chunk, bool finished) @safe chunkDelegate; 57 } 58 ///ditto 59 alias CSN = ColumnSpecialization; 60 61 @safe: 62 63 /++ 64 Execute an SQL command or prepared statement, such as INSERT/UPDATE/CREATE/etc. 65 66 This method is intended for commands such as which do not produce a result set 67 (otherwise, use one of the `query` functions instead.) If the SQL command does 68 produces a result set (such as SELECT), `mysql.exceptions.MYXResultRecieved` 69 will be thrown. 70 71 If `args` is supplied, the sql string will automatically be used as a prepared 72 statement. Prepared statements are automatically cached by mysql-native, 73 so there's no performance penalty for using this multiple times for the 74 same statement instead of manually preparing a statement. 75 76 If `args` and `prepared` are both provided, `args` will be used, 77 and any arguments that are already set in the prepared statement 78 will automatically be replaced with `args` (note, just like calling 79 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all 80 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied). 81 82 Only use the `const(char[]) sql` overload that doesn't take `args` 83 when you are not going to be using the same 84 command repeatedly and you are CERTAIN all the data you're sending is properly 85 escaped. Otherwise, consider using overload that takes a `Prepared`. 86 87 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use 88 `mysql.safe.connection.prepare` to manually create a 89 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using 90 `mysql.impl.prepared.SafePrepared.setArg` or 91 `mysql.impl.prepared.SafePrepared.setArgs`. 92 93 Type_Mappings: $(TYPE_MAPPINGS) 94 95 Params: 96 conn = An open `mysql.impl.connection.Connection` to the database. 97 sql = The SQL command to be run. 98 prepared = The prepared statement to be run. 99 args = The arguments to be passed in the `mysql.impl.prepared.SafePrepared`. 100 101 Returns: The number of rows affected. 102 103 Example: 104 --- 105 auto myInt = 7; 106 auto rowsAffected = myConnection.exec("INSERT INTO `myTable` (`a`) VALUES (?)", myInt); 107 --- 108 +/ 109 ulong exec(Connection conn, const(char[]) sql) 110 { 111 return execImpl(conn, ExecQueryImplInfo(false, sql)); 112 } 113 ///ditto 114 ulong exec(T...)(Connection conn, const(char[]) sql, T args) 115 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[])) 116 { 117 auto prepared = conn.prepare(sql); 118 prepared.setArgs(args); 119 return exec(conn, prepared); 120 } 121 ///ditto 122 ulong exec(Connection conn, const(char[]) sql, MySQLVal[] args) 123 { 124 auto prepared = conn.prepare(sql); 125 prepared.setArgs(args); 126 return exec(conn, prepared); 127 } 128 129 ///ditto 130 ulong exec(Connection conn, ref Prepared prepared) 131 { 132 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 133 auto ra = execImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 134 prepared._lastInsertID = conn.lastInsertID; 135 return ra; 136 } 137 ///ditto 138 ulong exec(T...)(Connection conn, ref Prepared prepared, T args) 139 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[])) 140 { 141 prepared.setArgs(args); 142 return exec(conn, prepared); 143 } 144 145 ///ditto 146 ulong exec(Connection conn, ref Prepared prepared, MySQLVal[] args) 147 { 148 prepared.setArgs(args); 149 return exec(conn, prepared); 150 } 151 152 /// Common implementation for `exec` overloads 153 package ulong execImpl(Connection conn, ExecQueryImplInfo info) 154 { 155 ulong rowsAffected; 156 bool receivedResultSet = execQueryImpl(conn, info, rowsAffected); 157 if(receivedResultSet) 158 { 159 conn.purgeResult(); 160 throw new MYXResultRecieved(); 161 } 162 163 return rowsAffected; 164 } 165 166 /++ 167 Execute an SQL SELECT command or prepared statement. 168 169 This returns an input range of `mysql.impl.result.SafeRow`, so if you need random 170 access to the `mysql.impl.result.SafeRow` elements, simply call 171 $(LINK2 https://dlang.org/phobos/std_array.html#array, `std.array.array()`) 172 on the result. 173 174 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 175 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 176 `exec` instead for such commands. 177 178 If `args` is supplied, the sql string will automatically be used as a prepared 179 statement. Prepared statements are automatically cached by mysql-native, 180 so there's no performance penalty for using this multiple times for the 181 same statement instead of manually preparing a statement. 182 183 If `args` and `prepared` are both provided, `args` will be used, 184 and any arguments that are already set in the prepared statement 185 will automatically be replaced with `args` (note, just like calling 186 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all 187 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied). 188 189 Only use the `const(char[]) sql` overload that doesn't take `args` 190 when you are not going to be using the same 191 command repeatedly and you are CERTAIN all the data you're sending is properly 192 escaped. Otherwise, consider using overload that takes a `Prepared`. 193 194 If you need to use any `mysql.safe.prepared.ParameterSpecialization`, use 195 `mysql.safe.connection.prepare` to manually create a 196 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using 197 `mysql.impl.prepared.SafePrepared.setArg` or 198 `mysql.impl.prepared.SafePrepared.setArgs`. 199 200 Type_Mappings: $(TYPE_MAPPINGS) 201 202 Params: 203 conn = An open `mysql.impl.connection.Connection` to the database. 204 sql = The SQL command to be run. 205 prepared = The prepared statement to be run. 206 csa = Not yet implemented. 207 args = Arguments to the SQL statement or `mysql.safe.prepared.Prepared` struct. 208 209 Returns: A (possibly empty) `mysql.safe.result.ResultRange`. 210 211 Example: 212 --- 213 ResultRange oneAtATime = myConnection.query("SELECT * from `myTable`"); 214 Row[] allAtOnce = myConnection.query("SELECT * from `myTable`").array; 215 216 auto myInt = 7; 217 ResultRange rows = myConnection.query("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 218 --- 219 +/ 220 /+ 221 Future text: 222 If there are long data items among the expected result columns you can use 223 the `csa` param to specify that they are to be subject to chunked transfer via a 224 delegate. 225 226 csa = An optional array of `ColumnSpecialization` structs. If you need to 227 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 228 +/ 229 SafeResultRange query(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null) 230 { 231 return queryImpl(csa, conn, ExecQueryImplInfo(false, sql)); 232 } 233 ///ditto 234 SafeResultRange query(T...)(Connection conn, const(char[]) sql, T args) 235 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 236 { 237 auto prepared = conn.prepare(sql); 238 prepared.setArgs(args); 239 return query(conn, prepared); 240 } 241 242 ///ditto 243 SafeResultRange query(Connection conn, const(char[]) sql, MySQLVal[] args) 244 { 245 auto prepared = conn.prepare(sql); 246 prepared.setArgs(args); 247 return query(conn, prepared); 248 } 249 250 ///ditto 251 SafeResultRange query(Connection conn, ref Prepared prepared) 252 { 253 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 254 auto result = queryImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 255 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 256 return result; 257 } 258 ///ditto 259 SafeResultRange query(T...)(Connection conn, ref Prepared prepared, T args) 260 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 261 { 262 prepared.setArgs(args); 263 return query(conn, prepared); 264 } 265 ///ditto 266 SafeResultRange query(Connection conn, ref Prepared prepared, MySQLVal[] args) 267 { 268 prepared.setArgs(args); 269 return query(conn, prepared); 270 } 271 272 /// Common implementation for `query` overloads 273 package SafeResultRange queryImpl(ColumnSpecialization[] csa, 274 Connection conn, ExecQueryImplInfo info) 275 { 276 ulong ra; 277 enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 278 279 conn._rsh = ResultSetHeaders(conn, conn._fieldCount); 280 if(csa !is null) 281 conn._rsh.addSpecializations(csa); 282 283 conn._headersPending = false; 284 return SafeResultRange(conn, conn._rsh, conn._rsh.fieldNames); 285 } 286 287 /++ 288 Execute an SQL SELECT command or prepared statement where you only want the 289 first `mysql.impl.result.SafeRow`, if any. 290 291 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 292 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 293 `exec` instead for such commands. 294 295 If `args` is supplied, the sql string will automatically be used as a prepared 296 statement. Prepared statements are automatically cached by mysql-native, 297 so there's no performance penalty for using this multiple times for the 298 same statement instead of manually preparing a statement. 299 300 If `args` and `prepared` are both provided, `args` will be used, 301 and any arguments that are already set in the prepared statement 302 will automatically be replaced with `args` (note, just like calling 303 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all 304 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied). 305 306 Only use the `const(char[]) sql` overload that doesn't take `args` 307 when you are not going to be using the same 308 command repeatedly and you are CERTAIN all the data you're sending is properly 309 escaped. Otherwise, consider using overload that takes a `Prepared`. 310 311 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use 312 `mysql.safe.connection.prepare` to manually create a 313 `mysql.impl.prepared.SafePrepared`, and set your parameter specializations using 314 `mysql.impl.prepared.SafePrepared.setArg` or 315 `mysql.impl.prepared.SafePrepared.setArgs`. 316 317 Type_Mappings: $(TYPE_MAPPINGS) 318 319 Params: 320 conn = An open `mysql.impl.connection.Connection` to the database. 321 sql = The SQL command to be run. 322 prepared = The prepared statement to be run. 323 csa = Not yet implemented. 324 args = Arguments to SQL statement or `mysql.impl.prepared.SafePrepared` struct. 325 326 Returns: `Nullable!(mysql.impl.result.SafeRow)`: This will be null (check 327 via `Nullable.isNull`) if the query resulted in an empty result set. 328 329 Example: 330 --- 331 auto myInt = 7; 332 Nullable!Row row = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 333 --- 334 +/ 335 /+ 336 Future text: 337 If there are long data items among the expected result columns you can use 338 the `csa` param to specify that they are to be subject to chunked transfer via a 339 delegate. 340 341 csa = An optional array of `ColumnSpecialization` structs. If you need to 342 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 343 +/ 344 /+ 345 Future text: 346 If there are long data items among the expected result columns you can use 347 the `csa` param to specify that they are to be subject to chunked transfer via a 348 delegate. 349 350 csa = An optional array of `ColumnSpecialization` structs. If you need to 351 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 352 +/ 353 Nullable!SafeRow queryRow(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null) 354 { 355 return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql)); 356 } 357 ///ditto 358 Nullable!SafeRow queryRow(T...)(Connection conn, const(char[]) sql, T args) 359 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 360 { 361 auto prepared = conn.prepare(sql); 362 prepared.setArgs(args); 363 return queryRow(conn, prepared); 364 } 365 ///ditto 366 Nullable!SafeRow queryRow(Connection conn, const(char[]) sql, MySQLVal[] args) 367 { 368 auto prepared = conn.prepare(sql); 369 prepared.setArgs(args); 370 return queryRow(conn, prepared); 371 } 372 373 ///ditto 374 Nullable!SafeRow queryRow(Connection conn, ref Prepared prepared) 375 { 376 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 377 auto result = queryRowImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 378 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 379 return result; 380 } 381 ///ditto 382 Nullable!SafeRow queryRow(T...)(Connection conn, ref Prepared prepared, T args) 383 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 384 { 385 prepared.setArgs(args); 386 return queryRow(conn, prepared); 387 } 388 ///ditto 389 Nullable!SafeRow queryRow(Connection conn, ref Prepared prepared, MySQLVal[] args) 390 { 391 prepared.setArgs(args); 392 return queryRow(conn, prepared); 393 } 394 395 /// Common implementation for `querySet` overloads. 396 package Nullable!SafeRow queryRowImpl(ColumnSpecialization[] csa, Connection conn, 397 ExecQueryImplInfo info) 398 { 399 auto results = queryImpl(csa, conn, info); 400 if(results.empty) 401 return Nullable!SafeRow(); 402 else 403 { 404 auto row = results.front; 405 results.close(); 406 return Nullable!SafeRow(row); 407 } 408 } 409 410 /++ 411 Execute an SQL SELECT command or prepared statement where you only want the 412 first `mysql.result.Row`, and place result values into a set of D variables. 413 414 This method will throw if any column type is incompatible with the corresponding D variable. 415 416 Unlike the other query functions, queryRowTuple will throw 417 `mysql.exceptions.MYX` if the result set is empty 418 (and thus the reference variables passed in cannot be filled). 419 420 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 421 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 422 `exec` instead for such commands. 423 424 Only use the `const(char[]) sql` overload when you are not going to be using the same 425 command repeatedly and you are CERTAIN all the data you're sending is properly 426 escaped. Otherwise, consider using overload that takes a `Prepared`. 427 428 Type_Mappings: $(TYPE_MAPPINGS) 429 430 Params: 431 conn = An open `mysql.impl.connection.Connection` to the database. 432 sql = The SQL command to be run. 433 prepared = The prepared statement to be run. 434 args = The variables, taken by reference, to receive the values. 435 +/ 436 void queryRowTuple(T...)(Connection conn, const(char[]) sql, ref T args) 437 { 438 return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args); 439 } 440 441 ///ditto 442 void queryRowTuple(T...)(Connection conn, ref Prepared prepared, ref T args) 443 { 444 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 445 queryRowTupleImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId), args); 446 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 447 } 448 449 /// Common implementation for `queryRowTuple` overloads. 450 package(mysql) void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args) 451 { 452 ulong ra; 453 enforce!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 454 455 auto rr = conn.getNextRow(); 456 /+if (!rr._valid) // The result set was empty - not a crime. 457 return;+/ 458 enforce!MYX(rr._values.length == args.length, "Result column count does not match the target tuple."); 459 foreach (size_t i, dummy; args) 460 { 461 import taggedalgebraic.taggedalgebraic : get, hasType; 462 enforce!MYX(rr._values[i].hasType!(T[i]), 463 "Tuple "~to!string(i)~" type and column type are not compatible."); 464 // use taggedalgebraic get to avoid extra calls. 465 args[i] = get!(T[i])(rr._values[i]); 466 } 467 // If there were more rows, flush them away 468 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 469 // allow sloppy SQL that does not ensure just one row! 470 conn.purgeResult(); 471 } 472 473 /++ 474 Execute an SQL SELECT command or prepared statement and return a single value: 475 the first column of the first row received. 476 477 If the query did not produce any rows, or the rows it produced have zero columns, 478 this will return `Nullable!MySQLVal()`, ie, null. Test for this with 479 `result.isNull`. 480 481 If the query DID produce a result, but the value actually received is NULL, 482 then `result.isNull` will be FALSE, and `result.get` will produce a MySQLVal 483 which CONTAINS null. Check for this with `result.get.kind == MySQLVal.Kind.Null` 484 or `result.get == null`. 485 486 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 487 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 488 `exec` instead for such commands. 489 490 If `args` is supplied, the sql string will automatically be used as a prepared 491 statement. Prepared statements are automatically cached by mysql-native, 492 so there's no performance penalty for using this multiple times for the 493 same statement instead of manually preparing a statement. 494 495 If `args` and `prepared` are both provided, `args` will be used, 496 and any arguments that are already set in the prepared statement 497 will automatically be replaced with `args` (note, just like calling 498 `mysql.impl.prepared.SafePrepared.setArgs`, this will also remove all 499 `mysql.impl.prepared.SafeParameterSpecialization` that may have been applied). 500 501 Only use the `const(char[]) sql` overload that doesn't take `args` 502 when you are not going to be using the same 503 command repeatedly and you are CERTAIN all the data you're sending is properly 504 escaped. Otherwise, consider using overload that takes a `Prepared`. 505 506 If you need to use any `mysql.impl.prepared.SafeParameterSpecialization`, use 507 `mysql.safe.connection.prepare` to manually create a `mysql.impl.prepared.SafePrepared`, 508 and set your parameter specializations using `mysql.impl.prepared.SafePrepared.setArg` 509 or `mysql.impl.prepared.SafePrepared.setArgs`. 510 511 Type_Mappings: $(TYPE_MAPPINGS) 512 513 Params: 514 conn = An open `mysql.impl.connection.Connection` to the database. 515 sql = The SQL command to be run. 516 prepared = The prepared statement to be run. 517 csa = Not yet implemented. 518 519 Returns: `Nullable!MySQLVal`: This will be null (check via `Nullable.isNull`) if the 520 query resulted in an empty result set. 521 522 Example: 523 --- 524 auto myInt = 7; 525 Nullable!MySQLVal value = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 526 --- 527 +/ 528 /+ 529 Future text: 530 If there are long data items among the expected result columns you can use 531 the `csa` param to specify that they are to be subject to chunked transfer via a 532 delegate. 533 534 csa = An optional array of `ColumnSpecialization` structs. If you need to 535 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 536 +/ 537 /+ 538 Future text: 539 If there are long data items among the expected result columns you can use 540 the `csa` param to specify that they are to be subject to chunked transfer via a 541 delegate. 542 543 csa = An optional array of `ColumnSpecialization` structs. If you need to 544 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 545 +/ 546 Nullable!MySQLVal queryValue(Connection conn, const(char[]) sql, ColumnSpecialization[] csa = null) 547 { 548 return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql)); 549 } 550 ///ditto 551 Nullable!MySQLVal queryValue(T...)(Connection conn, const(char[]) sql, T args) 552 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 553 { 554 auto prepared = conn.prepare(sql); 555 prepared.setArgs(args); 556 return queryValue(conn, prepared); 557 } 558 ///ditto 559 Nullable!MySQLVal queryValue(Connection conn, const(char[]) sql, MySQLVal[] args) 560 { 561 auto prepared = conn.prepare(sql); 562 prepared.setArgs(args); 563 return queryValue(conn, prepared); 564 } 565 ///ditto 566 Nullable!MySQLVal queryValue(Connection conn, ref Prepared prepared) 567 { 568 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 569 auto result = queryValueImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 570 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 571 return result; 572 } 573 ///ditto 574 Nullable!MySQLVal queryValue(T...)(Connection conn, ref Prepared prepared, T args) 575 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == MySQLVal[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 576 { 577 prepared.setArgs(args); 578 return queryValue(conn, prepared); 579 } 580 ///ditto 581 Nullable!MySQLVal queryValue(Connection conn, ref Prepared prepared, MySQLVal[] args) 582 { 583 prepared.setArgs(args); 584 return queryValue(conn, prepared); 585 } 586 587 /// Common implementation for `queryValue` overloads. 588 package Nullable!MySQLVal queryValueImpl(ColumnSpecialization[] csa, Connection conn, 589 ExecQueryImplInfo info) 590 { 591 auto results = queryImpl(csa, conn, info); 592 if(results.empty) 593 return Nullable!MySQLVal(); 594 else 595 { 596 auto row = results.front; 597 results.close(); 598 599 if(row.length == 0) 600 return Nullable!MySQLVal(); 601 else 602 return Nullable!MySQLVal(row[0]); 603 } 604 } 605