This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 77daf985d2d GH-49482: [C++][FlightRPC][ODBC] Fix inconsistent 
SQLGetInfo values in global connection (#50021)
77daf985d2d is described below

commit 77daf985d2d3b9172e00042141d4ab512e5c1cdf
Author: Kumar Vanshaj <[email protected]>
AuthorDate: Mon Aug 10 04:50:41 2026 +0530

    GH-49482: [C++][FlightRPC][ODBC] Fix inconsistent SQLGetInfo values in 
global connection (#50021)
    
    ## What changed
    - `SQL_DDL_SCHEMA` case now reads the boolean scalar to conditionally set 
`SQL_DROP_SCHEMA` and `SQL_CREATE_SCHEMA` to `0` when unsupported, instead of 
always writing non-zero values
    - `SQL_DDL_TABLE` case applies the same pattern for `SQL_DROP_TABLE` and 
`SQL_CREATE_TABLE`
    - `SQL_CATALOG_AT_START` changed to use `SetDefaultIfMissing` so it does 
not overwrite `SQL_CATALOG_LOCATION` already set by `ARROW_SQL_CATALOG_TERM`, 
eliminating an ordering-dependent conflict
    - Removed per-test connect/disconnect workarounds and converted three tests 
from `ConnectionInfoHandleTest` back to `ConnectionInfoTest`
    
    ## How to test
    - Build ODBC tests and run `TestSQLGetInfoCatalogLocation`, 
`TestSQLGetInfoDropSchema`, `TestSQLGetInfoDropTable` with both mock server and 
global connection fixture
    - All three tests should pass consistently regardless of connection reuse
    
    Closes #49482
    * GitHub Issue: #49482
    
    Lead-authored-by: vanshaj2023 <[email protected]>
    Co-authored-by: vanshaj2023 <[email protected]>
    Co-authored-by: Kumar Vanshaj <[email protected]>
    Co-authored-by: Alina (Xi) Li <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 .../flight/sql/odbc/odbc_impl/get_info_cache.cc    | 70 +++++++++++-----------
 .../flight/sql/odbc/tests/connection_info_test.cc  | 38 +++++-------
 2 files changed, 50 insertions(+), 58 deletions(-)

diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.cc
index 6923d7fafbe..1d9e9ee4c18 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/get_info_cache.cc
@@ -25,6 +25,7 @@
 #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
 #include "arrow/scalar.h"
 #include "arrow/type_fwd.h"
+#include "arrow/util/checked_cast.h"
 
 #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_stream_chunk_buffer.h"
 #include "arrow/flight/sql/odbc/odbc_impl/scalar_function_reporter.h"
@@ -76,6 +77,9 @@
 #define ARROW_CONVERT_VARCHAR 19
 
 namespace arrow::flight::sql::odbc {
+
+using arrow::internal::checked_cast;
+
 namespace {
 // Return the corresponding field in SQLGetInfo's SQL_CONVERT_* field
 // types for the given Arrow SqlConvert enum value.
@@ -190,7 +194,7 @@ inline int64_t ScalarToInt64(UnionScalar* scalar) {
 }
 
 inline std::string ScalarToBoolString(UnionScalar* scalar) {
-  return reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value 
? "Y" : "N";
+  return checked_cast<BooleanScalar*>(scalar->child_value().get())->value ? 
"Y" : "N";
 }
 
 inline void SetDefaultIfMissing(std::unordered_map<uint16_t, 
Connection::Info>& cache,
@@ -246,8 +250,6 @@ GetInfoCache::GetInfoCache(FlightClientOptions& 
client_options,
   info_[SQL_DROP_CHARACTER_SET] = static_cast<uint32_t>(0);
   info_[SQL_DROP_COLLATION] = static_cast<uint32_t>(0);
   info_[SQL_DROP_DOMAIN] = static_cast<uint32_t>(0);
-  info_[SQL_DROP_SCHEMA] = static_cast<uint32_t>(0);
-  info_[SQL_DROP_TABLE] = static_cast<uint32_t>(0);
   info_[SQL_DROP_TRANSLATION] = static_cast<uint32_t>(0);
   info_[SQL_DROP_VIEW] = static_cast<uint32_t>(0);
   info_[SQL_MAX_IDENTIFIER_LEN] = static_cast<uint16_t>(65535);  // arbitrary
@@ -395,25 +397,21 @@ bool GetInfoCache::LoadInfoFromServer() {
               // Unused by ODBC.
               break;
             case SqlInfoOptions::SQL_DDL_SCHEMA: {
-              // GH-49500 TODO: use scalar bool to determine 
`SQL_CREATE_SCHEMA` and
-              // `SQL_DROP_SCHEMA` values
-
-              // Note: this is a bitmask and we can't describe cascade or 
restrict
-              // flags.
-              info_[SQL_DROP_SCHEMA] = 
static_cast<uint32_t>(SQL_DS_DROP_SCHEMA);
-
-              // Note: this is a bitmask and we can't describe authorization or
-              // collation
-              info_[SQL_CREATE_SCHEMA] = 
static_cast<uint32_t>(SQL_CS_CREATE_SCHEMA);
+              bool supported =
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
+              info_[SQL_DROP_SCHEMA] =
+                  static_cast<uint32_t>(supported ? SQL_DS_DROP_SCHEMA : 0);
+              info_[SQL_CREATE_SCHEMA] =
+                  static_cast<uint32_t>(supported ? SQL_CS_CREATE_SCHEMA : 0);
               break;
             }
             case SqlInfoOptions::SQL_DDL_TABLE: {
-              // GH-49500 TODO: use scalar bool to determine 
`SQL_CREATE_TABLE` and
-              // `SQL_DROP_TABLE` values
-
-              // This is a bitmask and we cannot describe all clauses.
-              info_[SQL_CREATE_TABLE] = 
static_cast<uint32_t>(SQL_CT_CREATE_TABLE);
-              info_[SQL_DROP_TABLE] = static_cast<uint32_t>(SQL_DT_DROP_TABLE);
+              bool supported =
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
+              info_[SQL_CREATE_TABLE] =
+                  static_cast<uint32_t>(supported ? SQL_CT_CREATE_TABLE : 0);
+              info_[SQL_DROP_TABLE] =
+                  static_cast<uint32_t>(supported ? SQL_DT_DROP_TABLE : 0);
               break;
             }
             case SqlInfoOptions::SQL_ALL_TABLES_ARE_SELECTABLE: {
@@ -426,7 +424,7 @@ bool GetInfoCache::LoadInfoFromServer() {
             }
             case SqlInfoOptions::SQL_NULL_PLUS_NULL_IS_NULL: {
               info_[SQL_CONCAT_NULL_BEHAVIOR] = static_cast<uint16_t>(
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value
                       ? SQL_CB_NULL
                       : SQL_CB_NON_NULL);
               break;
@@ -436,7 +434,7 @@ bool GetInfoCache::LoadInfoFromServer() {
               // SQL_SUPPORTS_DIFFERENT_TABLE_CORRELATION_NAMES since we need 
both
               // properties to determine the value for SQL_CORRELATION_NAME.
               supports_correlation_name =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value;
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
               break;
             }
             case 
SqlInfoOptions::SQL_SUPPORTS_DIFFERENT_TABLE_CORRELATION_NAMES: {
@@ -444,7 +442,7 @@ bool GetInfoCache::LoadInfoFromServer() {
               // SQL_SUPPORTS_DIFFERENT_TABLE_CORRELATION_NAMES since we need 
both
               // properties to determine the value for SQL_CORRELATION_NAME.
               requires_different_correlation_name =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value;
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
               break;
             }
             case SqlInfoOptions::SQL_SUPPORTS_EXPRESSIONS_IN_ORDER_BY: {
@@ -454,9 +452,8 @@ bool GetInfoCache::LoadInfoFromServer() {
             case SqlInfoOptions::SQL_SUPPORTS_ORDER_BY_UNRELATED: {
               // Note: this is the negation of the Flight SQL property.
               info_[SQL_ORDER_BY_COLUMNS_IN_SELECT] =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value
-                      ? "N"
-                      : "Y";
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value ? "N"
+                                                                               
    : "Y";
               break;
             }
             case SqlInfoOptions::SQL_SUPPORTS_LIKE_ESCAPE_CLAUSE: {
@@ -465,7 +462,7 @@ bool GetInfoCache::LoadInfoFromServer() {
             }
             case SqlInfoOptions::SQL_SUPPORTS_NON_NULLABLE_COLUMNS: {
               info_[SQL_NON_NULLABLE_COLUMNS] = static_cast<uint16_t>(
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value
                       ? SQL_NNC_NON_NULL
                       : SQL_NNC_NULL);
               break;
@@ -475,10 +472,15 @@ bool GetInfoCache::LoadInfoFromServer() {
               break;
             }
             case SqlInfoOptions::SQL_CATALOG_AT_START: {
-              info_[SQL_CATALOG_LOCATION] = static_cast<uint16_t>(
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value
-                      ? SQL_CL_START
-                      : SQL_CL_END);
+              // Only use this as a fallback if ARROW_SQL_CATALOG_TERM has not 
already
+              // set SQL_CATALOG_LOCATION (to avoid conflicting writes 
depending on
+              // response key ordering).
+              SetDefaultIfMissing(
+                  info_, SQL_CATALOG_LOCATION,
+                  static_cast<uint16_t>(
+                      
checked_cast<BooleanScalar*>(scalar->child_value().get())->value
+                          ? SQL_CL_START
+                          : SQL_CL_END));
               break;
             }
             case SqlInfoOptions::SQL_SELECT_FOR_UPDATE_SUPPORTED:
@@ -494,22 +496,22 @@ bool GetInfoCache::LoadInfoFromServer() {
             }
             case SqlInfoOptions::SQL_TRANSACTIONS_SUPPORTED: {
               transactions_supported =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value;
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
               break;
             }
             case 
SqlInfoOptions::SQL_DATA_DEFINITION_CAUSES_TRANSACTION_COMMIT: {
               transaction_ddl_commit =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value;
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
               break;
             }
             case SqlInfoOptions::SQL_DATA_DEFINITIONS_IN_TRANSACTIONS_IGNORED: 
{
               transaction_ddl_ignore =
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value;
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value;
               break;
             }
             case SqlInfoOptions::SQL_BATCH_UPDATES_SUPPORTED: {
               info_[SQL_BATCH_SUPPORT] = static_cast<uint32_t>(
-                  
reinterpret_cast<BooleanScalar*>(scalar->child_value().get())->value
+                  
checked_cast<BooleanScalar*>(scalar->child_value().get())->value
                       ? SQL_BS_ROW_COUNT_EXPLICIT
                       : 0);
               break;
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc
index 232aa985c7e..ff4abc59fa1 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc
@@ -30,6 +30,7 @@ template <typename T>
 class ConnectionInfoTest : public T {};
 
 class ConnectionInfoMockTest : public FlightSQLODBCMockTestBase {};
+class ConnectionInfoRemoteTest : public FlightSQLODBCRemoteTestBase {};
 using TestTypes = ::testing::Types<ConnectionInfoMockTest, 
FlightSQLODBCRemoteTestBase>;
 TYPED_TEST_SUITE(ConnectionInfoTest, TestTypes);
 
@@ -622,18 +623,11 @@ TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoAlterTable) {
   EXPECT_EQ(static_cast<SQLUINTEGER>(0), value);
 }
 
-TYPED_TEST(ConnectionInfoHandleTest, TestSQLGetInfoCatalogLocation) {
-  // GH-49482 TODO: resolve inconsitent return value for SQL_CATALOG_LOCATION 
and change
-  // test type to `ConnectionInfoTest`
-  this->ConnectWithString(this->GetConnectionString(), this->conn);
-
+TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoCatalogLocation) {
   SQLUSMALLINT value;
   GetInfo(this->conn, SQL_CATALOG_LOCATION, &value);
 
   EXPECT_EQ(static_cast<SQLUSMALLINT>(0), value);
-
-  EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
-      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
 }
 
 TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoCatalogName) {
@@ -706,7 +700,8 @@ TEST_F(ConnectionInfoMockTest, TestSQLGetInfoCreateSchema) {
   SQLUINTEGER value;
   GetInfo(this->conn, SQL_CREATE_SCHEMA, &value);
 
-  EXPECT_EQ(static_cast<SQLUINTEGER>(1), value);
+  // SQLite (the mock backend) does not support schema DDL.
+  EXPECT_EQ(static_cast<SQLUINTEGER>(0), value);
 }
 
 TEST_F(ConnectionInfoMockTest, TestSQLGetInfoCreateTable) {
@@ -758,32 +753,27 @@ TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoDropDomain) {
   EXPECT_EQ(static_cast<SQLUINTEGER>(0), value);
 }
 
-TYPED_TEST(ConnectionInfoHandleTest, TestSQLGetInfoDropSchema) {
-  // GH-49482 TODO: resolve inconsitent return value for SQL_DROP_SCHEMA and 
change test
-  // type to `ConnectionInfoTest`
-  this->ConnectWithString(this->GetConnectionString(), this->conn);
-
+TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoDropSchema) {
   SQLUINTEGER value;
   GetInfo(this->conn, SQL_DROP_SCHEMA, &value);
 
+  // Neither the SQLite mock backend nor the Dremio backend support schema DDL.
   EXPECT_EQ(static_cast<SQLUINTEGER>(0), value);
-
-  EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
-      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
 }
 
-TYPED_TEST(ConnectionInfoHandleTest, TestSQLGetInfoDropTable) {
-  // GH-49482 TODO: resolve inconsitent return value for SQL_DROP_TABLE and 
change test
-  // type to `ConnectionInfoTest`
-  this->ConnectWithString(this->GetConnectionString(), this->conn);
+TEST_F(ConnectionInfoMockTest, TestSQLGetInfoDropTable) {
+  SQLUINTEGER value;
+  GetInfo(this->conn, SQL_DROP_TABLE, &value);
 
+  EXPECT_EQ(static_cast<SQLUINTEGER>(SQL_DT_DROP_TABLE), value);
+}
+
+TEST_F(ConnectionInfoRemoteTest, TestSQLGetInfoDropTable) {
   SQLUINTEGER value;
   GetInfo(this->conn, SQL_DROP_TABLE, &value);
 
+  // The Dremio backend does not report table DDL support.
   EXPECT_EQ(static_cast<SQLUINTEGER>(0), value);
-
-  EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
-      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
 }
 
 TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoDropTranslation) {

Reply via email to