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