diff --git a/src/fb-cpp/Statement.cpp b/src/fb-cpp/Statement.cpp index e63044e..2c901e0 100644 --- a/src/fb-cpp/Statement.cpp +++ b/src/fb-cpp/Statement.cpp @@ -51,7 +51,7 @@ Statement::Statement( flags |= fb::IStatement::PREPARE_PREFETCH_DETAILED_PLAN; statementHandle.reset(attachment.getHandle()->prepare(&statusWrapper, transaction.getHandle().get(), - static_cast(sql.length()), sql.data(), SQL_DIALECT_CURRENT, flags)); + static_cast(sql.length()), sql.data(), options.getDialect(), flags)); if (options.getCursorName().has_value()) statementHandle->setCursorName(&statusWrapper, options.getCursorName()->c_str()); diff --git a/src/fb-cpp/Statement.h b/src/fb-cpp/Statement.h index 2cd7491..396e7eb 100644 --- a/src/fb-cpp/Statement.h +++ b/src/fb-cpp/Statement.h @@ -164,11 +164,31 @@ namespace fbcpp return *this; } + /// + /// @brief Returns the SQL dialect used when preparing the statement. + /// + unsigned getDialect() const + { + return dialect; + } + + /// + /// @brief Sets the SQL dialect used when preparing the statement. + /// @param value SQL dialect number (1 for InterBase compatibility, 3 for current). + /// @return Reference to this instance for fluent configuration. + /// + StatementOptions& setDialect(unsigned value) + { + dialect = value; + return *this; + } + private: bool prefetchLegacyPlan = false; bool prefetchPlan = false; std::optional cursorName; CursorType cursorType = CursorType::FORWARD_ONLY; + unsigned dialect = SQL_DIALECT_CURRENT; }; /// diff --git a/src/test/Statement.cpp b/src/test/Statement.cpp index 3881560..e0a6d86 100644 --- a/src/test/Statement.cpp +++ b/src/test/Statement.cpp @@ -158,6 +158,57 @@ BOOST_AUTO_TEST_CASE(unsupportedStatementsThrow) BOOST_CHECK_THROW(Statement(attachment, transaction, "rollback"), FbCppException); } +BOOST_AUTO_TEST_CASE(dialectDefaultIsCurrent) +{ + StatementOptions options; + BOOST_CHECK_EQUAL(options.getDialect(), SQL_DIALECT_CURRENT); +} + +BOOST_AUTO_TEST_CASE(dialectSetterGetter) +{ + StatementOptions options; + options.setDialect(1u); + BOOST_CHECK_EQUAL(options.getDialect(), 1u); +} + +BOOST_AUTO_TEST_CASE(constructorWithExplicitDialect) +{ + const auto database = getTempFile("Statement-constructorWithExplicitDialect.fdb"); + + // Firebird 5 requires that statement dialect matches the database dialect, so + // the test database must be created with dialect 1. + const std::vector dialect1Dpb = { + isc_dpb_version1, + isc_dpb_sql_dialect, + 4, + 1, + 0, + 0, + 0, + }; + + Attachment attachment{CLIENT, database, AttachmentOptions().setCreateDatabase(true).setDpb(dialect1Dpb)}; + FbDropDatabase attachmentDrop{attachment}; + + Transaction transaction{attachment}; + + // SQL_DIALECT_CURRENT (3) does not match database dialect 1 and is rejected, + // proving statement dialect controls the behavior. + BOOST_CHECK_THROW( + Statement(attachment, transaction, "select cast(1234.56 as numeric(18, 2)) from rdb$database"), FbCppException); + + // With dialect 1 explicitly set, NUMERIC(18,2) is described as DOUBLE + // PRECISION and the value is retrieved via getDouble(). + Statement stmt{attachment, transaction, "select cast(1234.56 as numeric(18, 2)) from rdb$database", + StatementOptions().setDialect(1u)}; + BOOST_CHECK(stmt.isValid()); + BOOST_CHECK(stmt.getOutputDescriptors()[0].adjustedType == DescriptorAdjustedType::DOUBLE); + BOOST_REQUIRE(stmt.execute(transaction)); + auto value = stmt.getDouble(0); + BOOST_REQUIRE(value.has_value()); + BOOST_CHECK_CLOSE(*value, 1234.56, 0.001); +} + BOOST_AUTO_TEST_SUITE_END()