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 cb31765fe3 GH-47719: [C++][FlightRPC] Extract SQLTables Implementation 
(#48021)
cb31765fe3 is described below

commit cb31765fe32ef154a1c59447d1cfa339b57d85b3
Author: justing-bq <[email protected]>
AuthorDate: Tue Dec 9 06:05:00 2025 -0800

    GH-47719: [C++][FlightRPC] Extract SQLTables Implementation (#48021)
    
    ### Rationale for this change
    Addresses https://github.com/apache/arrow/issues/47719
    
    ### What changes are included in this PR?
    SQLTables enabled. Table tests added.
    
    ### Are these changes tested?
    Tested locally on MSVC.
    
    ### Are there any user-facing changes?
    No.
    * GitHub Issue: #47719
    
    Lead-authored-by: Alina (Xi) Li <[email protected]>
    Co-authored-by: justing-bq <[email protected]>
    Co-authored-by: alinalibq <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 cpp/src/arrow/flight/sql/odbc/odbc_api.cc          |  20 +-
 cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt |   1 +
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.cc |  16 +
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.h  |   6 +
 cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc | 490 +++++++++++++++++++++
 5 files changed, 531 insertions(+), 2 deletions(-)

diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 8ce3c77181..19f25c6a53 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -1182,8 +1182,24 @@ SQLRETURN SQLTables(SQLHSTMT stmt, SQLWCHAR* 
catalog_name,
                    << ", table_name_length: " << table_name_length
                    << ", table_type: " << static_cast<const void*>(table_type)
                    << ", table_type_length: " << table_type_length;
-  // GH-47719 TODO: Implement SQLTables
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCStatement;
+  using ODBC::SqlWcharToString;
+
+  return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+    ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+    std::string catalog = SqlWcharToString(catalog_name, catalog_name_length);
+    std::string schema = SqlWcharToString(schema_name, schema_name_length);
+    std::string table = SqlWcharToString(table_name, table_name_length);
+    std::string type = SqlWcharToString(table_type, table_type_length);
+
+    statement->GetTables(catalog_name ? &catalog : nullptr,
+                         schema_name ? &schema : nullptr, table_name ? &table 
: nullptr,
+                         table_type ? &type : nullptr);
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLColumns(SQLHSTMT stmt, SQLWCHAR* catalog_name,
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt 
b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
index fe27097a37..e2f83dcdbf 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -40,6 +40,7 @@ add_arrow_test(flight_sql_odbc_test
                errors_test.cc
                statement_attr_test.cc
                statement_test.cc
+               tables_test.cc
                # Enable Protobuf cleanup after test execution
                # GH-46889: move protobuf_test_util to a more common location
                ../../../../engine/substrait/protobuf_test_util.cc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
index c9ee71201f..e50b552175 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
@@ -481,6 +481,22 @@ bool WriteDSN(Connection::ConnPropertyMap properties) {
 }
 #endif
 
+std::wstring GetStringColumnW(SQLHSTMT stmt, int col_id) {
+  SQLWCHAR buf[1024];
+  SQLLEN len_indicator = 0;
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLGetData(stmt, col_id, SQL_C_WCHAR, buf, sizeof(buf), 
&len_indicator));
+
+  if (len_indicator == SQL_NULL_DATA) {
+    return L"";
+  }
+
+  // indicator is in bytes, so convert to character count
+  size_t char_count = static_cast<size_t>(len_indicator) / GetSqlWCharSize();
+  return std::wstring(buf, buf + char_count);
+}
+
 std::wstring ConvertToWString(const std::vector<SQLWCHAR>& str_val, 
SQLSMALLINT str_len) {
   std::wstring attr_str;
   if (str_len == 0) {
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
index 3915218b7b..c349704cc5 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
@@ -250,6 +250,12 @@ bool WriteDSN(std::string connection_str);
 /// \return true on success
 bool WriteDSN(Connection::ConnPropertyMap properties);
 
+/// \brief Get wide string column.
+/// \param[in] stmt Statement.
+/// \param[in] col_id Column ID to check.
+/// \return wstring
+std::wstring GetStringColumnW(SQLHSTMT stmt, int col_id);
+
 /// \brief Check wide char vector and convert into wstring
 /// \param[in] str_val Vector of SQLWCHAR.
 /// \param[in] str_len length of string, in bytes.
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc
new file mode 100644
index 0000000000..01a19337f4
--- /dev/null
+++ b/cpp/src/arrow/flight/sql/odbc/tests/tables_test.cc
@@ -0,0 +1,490 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h"
+
+#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
+
+#include <sql.h>
+#include <sqltypes.h>
+#include <sqlucode.h>
+
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+template <typename T>
+class TablesTest : public T {};
+
+class TablesMockTest : public FlightSQLODBCMockTestBase {};
+class TablesRemoteTest : public FlightSQLODBCRemoteTestBase {};
+using TestTypes = ::testing::Types<TablesRemoteTest, TablesMockTest>;
+TYPED_TEST_SUITE(TablesTest, TestTypes);
+
+template <typename T>
+class TablesOdbcV2Test : public T {};
+
+using TestTypesOdbcV2 =
+    ::testing::Types<FlightSQLOdbcV2MockTestBase, 
FlightSQLOdbcV2RemoteTestBase>;
+TYPED_TEST_SUITE(TablesOdbcV2Test, TestTypesOdbcV2);
+
+// Test Cases
+
+TYPED_TEST(TablesTest, SQLTablesTestInputData) {
+  SQLWCHAR catalog_name[] = L"";
+  SQLWCHAR schema_name[] = L"";
+  SQLWCHAR table_name[] = L"";
+  SQLWCHAR table_type[] = L"";
+
+  // All values populated
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, catalog_name, 
sizeof(catalog_name),
+                                   schema_name, sizeof(schema_name), 
table_name,
+                                   sizeof(table_name), table_type, 
sizeof(table_type)));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  // Sizes are zeros
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, catalog_name, 0, schema_name, 0,
+                                   table_name, 0, table_type, 0));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  // Names are nulls
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, sizeof(catalog_name), 
nullptr,
+                                   sizeof(schema_name), nullptr, 
sizeof(table_name),
+                                   nullptr, sizeof(table_type)));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+  // Close statement cursor to avoid leaving in an invalid state
+  SQLFreeStmt(this->stmt, SQL_CLOSE);
+
+  // Names are nulls and sizes are zeros
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLTables(this->stmt, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 
0));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForAllCatalogs) {
+  SQLWCHAR empty[] = L"";
+  SQLWCHAR SQL_ALL_CATALOGS_W[] = L"%";
+  std::wstring expected_catalog_name = std::wstring(L"main");
+
+  // Get Catalog metadata
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, SQL_ALL_CATALOGS_W, SQL_NTS, 
empty,
+                                   SQL_NTS, empty, SQL_NTS, empty, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+
+  CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+  CheckNullColumnW(this->stmt, 2);
+  CheckNullColumnW(this->stmt, 3);
+  CheckNullColumnW(this->stmt, 4);
+  CheckNullColumnW(this->stmt, 5);
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForNamedCatalog) {
+  this->CreateTestTables();
+
+  SQLWCHAR catalog_name[] = L"main";
+  const SQLWCHAR* table_names[] = {static_cast<const SQLWCHAR*>(L"TestTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"foreignTable"),
+                                   static_cast<const SQLWCHAR*>(L"intTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"sqlite_sequence")};
+  std::wstring expected_catalog_name = std::wstring(catalog_name);
+  std::wstring expected_table_type = std::wstring(L"table");
+
+  // Get named Catalog metadata - Mock server returns the system table 
sqlite_sequence as
+  // type "table"
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, catalog_name, SQL_NTS, nullptr, 
SQL_NTS,
+                                   nullptr, SQL_NTS, nullptr, SQL_NTS));
+
+  for (size_t i = 0; i < sizeof(table_names) / sizeof(*table_names); ++i) {
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+    // Mock server does not support table schema
+    CheckNullColumnW(this->stmt, 2);
+    CheckStringColumnW(this->stmt, 3, table_names[i]);
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+  }
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetSchemaHasNoData) {
+  SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%";
+
+  // Validate that no schema data is available for Mock server
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, 
SQL_ALL_SCHEMAS_W,
+                                   SQL_NTS, nullptr, SQL_NTS, nullptr, 
SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesRemoteTest, SQLTablesTestGetMetadataForAllSchemas) {
+  SQLWCHAR empty[] = L"";
+  SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%";
+  std::set<std::wstring> actual_schemas;
+  std::set<std::wstring> expected_schemas = {L"$scratch", 
L"INFORMATION_SCHEMA", L"sys",
+                                             L"sys.cache"};
+
+  // Return is unordered and contains user specific schemas, so collect schema 
names for
+  // comparison with a known list
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, empty, SQL_NTS, 
SQL_ALL_SCHEMAS_W, SQL_NTS,
+                                   empty, SQL_NTS, empty, SQL_NTS));
+
+  while (true) {
+    SQLRETURN ret = SQLFetch(this->stmt);
+    if (ret == SQL_NO_DATA) break;
+    ASSERT_EQ(SQL_SUCCESS, ret);
+
+    CheckNullColumnW(this->stmt, 1);
+    std::wstring schema = GetStringColumnW(this->stmt, 2);
+    CheckNullColumnW(this->stmt, 3);
+    CheckNullColumnW(this->stmt, 4);
+    CheckNullColumnW(this->stmt, 5);
+
+    // Skip user-specific schemas like "@UserName"
+    if (!schema.empty() && schema[0] != L'@') {
+      actual_schemas.insert(schema);
+    }
+  }
+
+  EXPECT_EQ(actual_schemas, expected_schemas);
+}
+
+TEST_F(TablesRemoteTest, SQLTablesTestFilterByAllSchema) {
+  // Requires creation of user table named ODBCTest using schema $scratch in 
remote server
+  SQLWCHAR SQL_ALL_SCHEMAS_W[] = L"%";
+  const SQLWCHAR* schema_names[] = {static_cast<const 
SQLWCHAR*>(L"INFORMATION_SCHEMA"),
+                                    static_cast<const 
SQLWCHAR*>(L"INFORMATION_SCHEMA"),
+                                    static_cast<const 
SQLWCHAR*>(L"INFORMATION_SCHEMA"),
+                                    static_cast<const 
SQLWCHAR*>(L"INFORMATION_SCHEMA"),
+                                    static_cast<const 
SQLWCHAR*>(L"INFORMATION_SCHEMA"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys"),
+                                    static_cast<const SQLWCHAR*>(L"sys.cache"),
+                                    static_cast<const SQLWCHAR*>(L"sys.cache"),
+                                    static_cast<const SQLWCHAR*>(L"sys.cache"),
+                                    static_cast<const SQLWCHAR*>(L"sys.cache"),
+                                    static_cast<const SQLWCHAR*>(L"$scratch")};
+  std::wstring expected_system_table_type = std::wstring(L"SYSTEM_TABLE");
+  std::wstring expected_user_table_type = std::wstring(L"TABLE");
+
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, 
SQL_ALL_SCHEMAS_W,
+                                   SQL_NTS, nullptr, SQL_NTS, nullptr, 
SQL_NTS));
+
+  for (size_t i = 0; i < sizeof(schema_names) / sizeof(*schema_names); ++i) {
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    const std::wstring& expected_table_type =
+        (std::wstring(schema_names[i]).rfind(L"sys", 0) == 0 ||
+         std::wstring(schema_names[i]) == L"INFORMATION_SCHEMA")
+            ? expected_system_table_type
+            : expected_user_table_type;
+
+    CheckNullColumnW(this->stmt, 1);
+    CheckStringColumnW(this->stmt, 2, schema_names[i]);
+    // Ignore table name
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+  }
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesRemoteTest, SQLTablesGetMetadataForNamedSchema) {
+  // Requires creation of user table named ODBCTest using schema $scratch in 
remote server
+  SQLWCHAR schema_name[] = L"$scratch";
+  std::wstring expected_schema_name = std::wstring(schema_name);
+  std::wstring expected_table_name = std::wstring(L"ODBCTest");
+  std::wstring expected_table_type = std::wstring(L"TABLE");
+
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, schema_name, 
SQL_NTS,
+                                   nullptr, SQL_NTS, nullptr, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+
+  CheckNullColumnW(this->stmt, 1);
+  CheckStringColumnW(this->stmt, 2, expected_schema_name);
+  // Ignore table name
+  CheckStringColumnW(this->stmt, 4, expected_table_type);
+  CheckNullColumnW(this->stmt, 5);
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForAllTables) {
+  this->CreateTestTables();
+
+  SQLWCHAR SQL_ALL_TABLES_W[] = L"%";
+  const SQLWCHAR* table_names[] = {static_cast<const SQLWCHAR*>(L"TestTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"foreignTable"),
+                                   static_cast<const SQLWCHAR*>(L"intTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"sqlite_sequence")};
+  std::wstring expected_catalog_name = std::wstring(L"main");
+  std::wstring expected_table_type = std::wstring(L"table");
+
+  // Get all Table metadata - Mock server returns the system table 
sqlite_sequence as type
+  // "table"
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   SQL_ALL_TABLES_W, SQL_NTS, nullptr, 
SQL_NTS));
+
+  for (size_t i = 0; i < sizeof(table_names) / sizeof(*table_names); ++i) {
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+    // Mock server does not support table schema
+    CheckNullColumnW(this->stmt, 2);
+    CheckStringColumnW(this->stmt, 3, table_names[i]);
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+  }
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForTableName) {
+  this->CreateTestTables();
+
+  // Use mutable arrays to pass SQLWCHAR parameters to SQLTables
+  SQLWCHAR test_table[] = L"TestTable";
+  SQLWCHAR foreign_table[] = L"foreignTable";
+  SQLWCHAR int_table[] = L"intTable";
+  SQLWCHAR sqlite_sequence[] = L"sqlite_sequence";
+
+  SQLWCHAR* table_names[] = {test_table, foreign_table, int_table, 
sqlite_sequence};
+
+  std::wstring expected_catalog_name = std::wstring(L"main");
+  std::wstring expected_table_type = std::wstring(L"table");
+
+  for (size_t i = 0; i < sizeof(table_names) / sizeof(*table_names); ++i) {
+    //  Get specific Table metadata
+    ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                     table_names[i], SQL_NTS, nullptr, 
SQL_NTS));
+
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+    // Mock server does not support table schema
+    CheckNullColumnW(this->stmt, 2);
+    CheckStringColumnW(this->stmt, 3, table_names[i]);
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+
+    ValidateFetch(this->stmt, SQL_NO_DATA);
+  }
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForUnicodeTableByTableName) {
+  this->CreateUnicodeTable();
+
+  SQLWCHAR unicodetable_name[] = L"数据";
+  std::wstring expected_catalog_name = std::wstring(L"main");
+  std::wstring expected_table_name = std::wstring(unicodetable_name);
+  std::wstring expected_table_type = std::wstring(L"table");
+
+  //  Get specific Table metadata
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   unicodetable_name, SQL_NTS, nullptr, 
SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+
+  CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+  // Mock server does not support table schema
+  CheckNullColumnW(this->stmt, 2);
+  CheckStringColumnW(this->stmt, 3, expected_table_name);
+  CheckStringColumnW(this->stmt, 4, expected_table_type);
+  CheckNullColumnW(this->stmt, 5);
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesTestGetMetadataForInvalidTableNameNoData) {
+  this->CreateTestTables();
+
+  SQLWCHAR invalid_table_name[] = L"NonExistanttable_name";
+
+  //  Try to get metadata for a non-existant table name
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   invalid_table_name, SQL_NTS, nullptr, 
SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesGetMetadataForTableType) {
+  // Mock server only supports table type "table" in lowercase
+  this->CreateTestTables();
+
+  SQLWCHAR table_type_table_lowercase[] = L"table";
+  SQLWCHAR table_type_table_uppercase[] = L"TABLE";
+  SQLWCHAR table_type_view[] = L"VIEW";
+  SQLWCHAR table_type_table_view[] = L"TABLE,VIEW";
+  const SQLWCHAR* table_names[] = {static_cast<const SQLWCHAR*>(L"TestTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"foreignTable"),
+                                   static_cast<const SQLWCHAR*>(L"intTable"),
+                                   static_cast<const 
SQLWCHAR*>(L"sqlite_sequence")};
+  std::wstring expected_catalog_name = std::wstring(L"main");
+  std::wstring expected_table_name = std::wstring(L"TestTable");
+  std::wstring expected_table_type = std::wstring(table_type_table_lowercase);
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                      table_type_table_uppercase, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   nullptr, SQL_NTS, table_type_view, 
SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   nullptr, SQL_NTS, table_type_table_view, 
SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  // Returns user table as well as system tables, even though only type table 
requested
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                      table_type_table_lowercase, SQL_NTS));
+
+  for (size_t i = 0; i < sizeof(table_names) / sizeof(*table_names); ++i) {
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckStringColumnW(this->stmt, 1, expected_catalog_name);
+    // Mock server does not support table schema
+    CheckNullColumnW(this->stmt, 2);
+    CheckStringColumnW(this->stmt, 3, table_names[i]);
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+  }
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesRemoteTest, SQLTablesGetMetadataForTableTypeTable) {
+  // Requires creation of user table named ODBCTest using schema $scratch in 
remote server
+
+  // Use mutable arrays to pass SQLWCHAR parameters to SQLTables
+  SQLWCHAR table[] = L"TABLE";
+  SQLWCHAR table_view[] = L"TABLE,VIEW";
+
+  SQLWCHAR* type_list[] = {table, table_view};
+
+  std::wstring expected_schema_name = std::wstring(L"$scratch");
+  std::wstring expected_table_name = std::wstring(L"ODBCTest");
+  std::wstring expected_table_type = std::wstring(L"TABLE");
+
+  for (size_t i = 0; i < sizeof(type_list) / sizeof(*type_list); ++i) {
+    ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                     nullptr, SQL_NTS, type_list[i], SQL_NTS));
+
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckNullColumnW(this->stmt, 1);
+    CheckStringColumnW(this->stmt, 2, expected_schema_name);
+    CheckStringColumnW(this->stmt, 3, expected_table_name);
+    CheckStringColumnW(this->stmt, 4, expected_table_type);
+    CheckNullColumnW(this->stmt, 5);
+
+    ValidateFetch(this->stmt, SQL_NO_DATA);
+  }
+}
+
+TEST_F(TablesRemoteTest, SQLTablesGetMetadataForTableTypeViewHasNoData) {
+  SQLWCHAR empty[] = L"";
+  SQLWCHAR type_view[] = L"VIEW";
+
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS, empty,
+                                   SQL_NTS, type_view, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+
+  EXPECT_EQ(SQL_SUCCESS, SQLTables(this->stmt, nullptr, SQL_NTS, nullptr, 
SQL_NTS,
+                                   nullptr, SQL_NTS, type_view, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesMockTest, SQLTablesGetSupportedTableTypes) {
+  SQLWCHAR empty[] = L"";
+  SQLWCHAR SQL_ALL_TABLE_TYPES_W[] = L"%";
+  std::wstring expected_table_type = std::wstring(L"table");
+
+  // Mock server returns lower case for supported type of "table"
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, empty, SQL_NTS, empty, SQL_NTS, 
empty,
+                                   SQL_NTS, SQL_ALL_TABLE_TYPES_W, SQL_NTS));
+
+  ValidateFetch(this->stmt, SQL_SUCCESS);
+
+  CheckNullColumnW(this->stmt, 1);
+  CheckNullColumnW(this->stmt, 2);
+  CheckNullColumnW(this->stmt, 3);
+  CheckStringColumnW(this->stmt, 4, expected_table_type);
+  CheckNullColumnW(this->stmt, 5);
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+TEST_F(TablesRemoteTest, SQLTablesGetSupportedTableTypes) {
+  SQLWCHAR empty[] = L"";
+  SQLWCHAR SQL_ALL_TABLE_TYPES_W[] = L"%";
+  const SQLWCHAR* type_lists[] = {static_cast<const SQLWCHAR*>(L"TABLE"),
+                                  static_cast<const 
SQLWCHAR*>(L"SYSTEM_TABLE"),
+                                  static_cast<const SQLWCHAR*>(L"VIEW")};
+
+  ASSERT_EQ(SQL_SUCCESS, SQLTables(this->stmt, empty, SQL_NTS, empty, SQL_NTS, 
empty,
+                                   SQL_NTS, SQL_ALL_TABLE_TYPES_W, SQL_NTS));
+
+  for (size_t i = 0; i < sizeof(type_lists) / sizeof(*type_lists); ++i) {
+    ValidateFetch(this->stmt, SQL_SUCCESS);
+
+    CheckNullColumnW(this->stmt, 1);
+    CheckNullColumnW(this->stmt, 2);
+    CheckNullColumnW(this->stmt, 3);
+    CheckStringColumnW(this->stmt, 4, type_lists[i]);
+    CheckNullColumnW(this->stmt, 5);
+  }
+
+  ValidateFetch(this->stmt, SQL_NO_DATA);
+}
+
+}  // namespace arrow::flight::sql::odbc

Reply via email to