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