@@ -342,6 +342,171 @@ BOOST_AUTO_TEST_CASE(queryScalarThrowsForNonQueryStatement)
342342 transaction.commit ();
343343}
344344
345+ BOOST_AUTO_TEST_CASE (queryFirstRowAsReturnsStructFromFirstRow)
346+ {
347+ struct Result
348+ {
349+ std::int32_t id;
350+ std::optional<std::string> name;
351+ };
352+
353+ const auto database = getTempFile (" Attachment-queryFirstRowAsReturnsStructFromFirstRow.fdb" );
354+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
355+ FbDropDatabase attachmentDrop{attachment};
356+
357+ Transaction transaction{attachment};
358+ BOOST_REQUIRE (
359+ attachment.execute (transaction, " create table t (id integer not null primary key, name varchar(20))" ));
360+ transaction.commitRetaining ();
361+
362+ BOOST_REQUIRE (attachment.execute (transaction, " insert into t (id, name) values (1, 'one')" ));
363+ BOOST_REQUIRE (attachment.execute (transaction, " insert into t (id, name) values (2, 'two')" ));
364+
365+ const auto value = attachment.queryFirstRowAs <Result>(transaction, " select id, name from t order by id" );
366+
367+ BOOST_REQUIRE (value.has_value ());
368+ BOOST_CHECK_EQUAL (value->id , 1 );
369+ BOOST_REQUIRE (value->name .has_value ());
370+ BOOST_CHECK_EQUAL (*value->name , " one" );
371+
372+ transaction.commit ();
373+ }
374+
375+ BOOST_AUTO_TEST_CASE (queryFirstRowAsReturnsTupleFromFirstRow)
376+ {
377+ using Result = std::tuple<std::int32_t , std::optional<std::string>>;
378+
379+ const auto database = getTempFile (" Attachment-queryFirstRowAsReturnsTupleFromFirstRow.fdb" );
380+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
381+ FbDropDatabase attachmentDrop{attachment};
382+
383+ Transaction transaction{attachment};
384+ const auto value = attachment.queryFirstRowAs <Result>(transaction, " select 42, 'answer' from rdb$database" );
385+
386+ BOOST_REQUIRE (value.has_value ());
387+ BOOST_CHECK_EQUAL (std::get<0 >(*value), 42 );
388+ BOOST_REQUIRE (std::get<1 >(*value).has_value ());
389+ BOOST_CHECK_EQUAL (*std::get<1 >(*value), " answer" );
390+
391+ transaction.commit ();
392+ }
393+
394+ BOOST_AUTO_TEST_CASE (queryFirstRowAsReturnsNulloptForNoRows)
395+ {
396+ struct Result
397+ {
398+ std::int32_t id;
399+ };
400+
401+ const auto database = getTempFile (" Attachment-queryFirstRowAsReturnsNulloptForNoRows.fdb" );
402+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
403+ FbDropDatabase attachmentDrop{attachment};
404+
405+ Transaction transaction{attachment};
406+ const auto value = attachment.queryFirstRowAs <Result>(transaction, " select 1 from rdb$database where 1 = 0" );
407+
408+ BOOST_CHECK (!value.has_value ());
409+
410+ transaction.commit ();
411+ }
412+
413+ BOOST_AUTO_TEST_CASE (queryFirstRowAsSupportsStatementOptions)
414+ {
415+ using Result = std::tuple<std::int32_t >;
416+
417+ const auto database = getTempFile (" Attachment-queryFirstRowAsSupportsStatementOptions.fdb" );
418+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
419+ FbDropDatabase attachmentDrop{attachment};
420+
421+ Transaction transaction{attachment};
422+ const auto value = attachment.queryFirstRowAs <Result>(
423+ transaction, " select 1 from rdb$database" , StatementOptions ().setDialect (3u ));
424+
425+ BOOST_REQUIRE (value.has_value ());
426+ BOOST_CHECK_EQUAL (std::get<0 >(*value), 1 );
427+
428+ transaction.commit ();
429+ }
430+
431+ BOOST_AUTO_TEST_CASE (queryFirstRowAsSupportsProcedureWithOutputColumns)
432+ {
433+ struct Result
434+ {
435+ std::int32_t id;
436+ std::optional<std::string> name;
437+ };
438+
439+ const auto database = getTempFile (" Attachment-queryFirstRowAsSupportsProcedureWithOutputColumns.fdb" );
440+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
441+ FbDropDatabase attachmentDrop{attachment};
442+
443+ Transaction transaction{attachment};
444+ BOOST_REQUIRE (attachment.execute (transaction,
445+ " create procedure p returns (id integer, name varchar(20)) as begin id = 42; name = 'answer'; suspend; end" ));
446+ transaction.commitRetaining ();
447+
448+ const auto value = attachment.queryFirstRowAs <Result>(transaction, " execute procedure p" );
449+
450+ BOOST_REQUIRE (value.has_value ());
451+ BOOST_CHECK_EQUAL (value->id , 42 );
452+ BOOST_REQUIRE (value->name .has_value ());
453+ BOOST_CHECK_EQUAL (*value->name , " answer" );
454+
455+ transaction.commit ();
456+ }
457+
458+ BOOST_AUTO_TEST_CASE (queryFirstRowAsThrowsForFieldCountMismatch)
459+ {
460+ struct Result
461+ {
462+ std::int32_t id;
463+ };
464+
465+ const auto database = getTempFile (" Attachment-queryFirstRowAsThrowsForFieldCountMismatch.fdb" );
466+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
467+ FbDropDatabase attachmentDrop{attachment};
468+
469+ Transaction transaction{attachment};
470+ BOOST_CHECK_THROW (attachment.queryFirstRowAs <Result>(transaction, " select 1, 2 from rdb$database" ), FbCppException);
471+
472+ transaction.commit ();
473+ }
474+
475+ BOOST_AUTO_TEST_CASE (queryFirstRowAsThrowsForNullIntoNonOptionalField)
476+ {
477+ struct Result
478+ {
479+ std::int32_t id;
480+ };
481+
482+ const auto database = getTempFile (" Attachment-queryFirstRowAsThrowsForNullIntoNonOptionalField.fdb" );
483+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
484+ FbDropDatabase attachmentDrop{attachment};
485+
486+ Transaction transaction{attachment};
487+ BOOST_CHECK_THROW (attachment.queryFirstRowAs <Result>(transaction, " select cast(null as integer) from rdb$database" ),
488+ FbCppException);
489+
490+ transaction.commit ();
491+ }
492+
493+ BOOST_AUTO_TEST_CASE (queryFirstRowAsThrowsForNonQueryStatement)
494+ {
495+ struct Result
496+ {
497+ std::int32_t id;
498+ };
499+
500+ const auto database = getTempFile (" Attachment-queryFirstRowAsThrowsForNonQueryStatement.fdb" );
501+ Attachment attachment{CLIENT , database, AttachmentOptions ().setCreateDatabase (true ).setForcedWrites (false )};
502+ FbDropDatabase attachmentDrop{attachment};
503+
504+ Transaction transaction{attachment};
505+ BOOST_CHECK_THROW (attachment.queryFirstRowAs <Result>(transaction, " create table t (id integer)" ), FbCppException);
506+
507+ transaction.commit ();
508+ }
509+
345510BOOST_AUTO_TEST_CASE (isNotValidAfterMove)
346511{
347512 const auto database = getTempFile (" Attachment-isNotValidAfterMove.fdb" );
0 commit comments