From 7bf555ef889138384e2f5b37d95730e479be671d Mon Sep 17 00:00:00 2001 From: Anshu6250 Date: Fri, 22 May 2026 11:45:35 +0530 Subject: [PATCH] dummy PR for perf benchmark with powerBI --- .../internal/odbc_internal_commons.cc | 10 + .../bq_driver/internal/odbc_sql_columns.cc | 49 +- .../bq_driver/internal/odbc_sql_tables.cc | 59 ++ .../odbc/bq_driver/odbc_driver_metadata.cc | 32 +- .../odbc_driver_tests/catalog_test.cc | 787 +++++++++++++++++- 5 files changed, 897 insertions(+), 40 deletions(-) diff --git a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.cc b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.cc index 1b933dd91e..beb39e8f06 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_internal_commons.cc @@ -827,7 +827,17 @@ StatusRecordOr PostQueryWithoutResults( } // For now , we use default options. // We can set timeout here as needed later. + + // --- BENCHMARK START: PostQuery (Client Library Call) --- + auto start_post_query = std::chrono::high_resolution_clock::now(); + auto pq_status = bq_client->PostQuery(post_query_request, options); + + // --- BENCHMARK END: PostQuery (Client Library Call) --- + auto end_post_query = std::chrono::high_resolution_clock::now(); + auto elapsed_post_query = std::chrono::duration_cast(end_post_query - start_post_query); + std::cout << "[BENCHMARK] PostQueryWithoutResults -> bq_client->PostQuery: " << elapsed_post_query.count() << " ms\n"; + if (!pq_status) { LOG(ERROR) << "PostQueryWithoutResults::PostQuery:: " << pq_status.GetStatusRecord().message; diff --git a/google/cloud/odbc/bq_driver/internal/odbc_sql_columns.cc b/google/cloud/odbc/bq_driver/internal/odbc_sql_columns.cc index 18c7ff28e8..6be2854c0f 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_sql_columns.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_sql_columns.cc @@ -417,15 +417,24 @@ StatusRecordOr> FetchBQTablesData( SQLStates::k_HY000(), "Invalid or null BQ Client within the connection handle"}; } + + // --- BENCHMARK START: GetFilteredDatasetIds --- + auto start_datasets = std::chrono::high_resolution_clock::now(); + // Get Datasets based on search pattern in the dataset argument StatusRecordOr> datasets_status = GetFilteredDatasetIds(*bq_client, catalog, dataset_pattern, metadata_id); + + // --- BENCHMARK END: GetFilteredDatasetIds --- + auto end_datasets = std::chrono::high_resolution_clock::now(); + auto elapsed_datasets = std::chrono::duration_cast(end_datasets - start_datasets); + std::cout << "[BENCHMARK] FetchBQTablesData -> GetFilteredDatasetIds: " << elapsed_datasets.count() << " ms\n"; + if (!datasets_status) { LOG(ERROR) << "FetchBQTablesData::GetFilteredDatasetIds:: " << datasets_status.GetStatusRecord().message; return datasets_status.GetStatusRecord(); } - struct TableTaskInput { std::size_t index; std::string dataset; @@ -440,13 +449,11 @@ StatusRecordOr> FetchBQTablesData( std::string dataset; std::vector table_names; }; - std::vector dataset_tasks; dataset_tasks.reserve(datasets_status->size()); for (std::size_t i = 0; i < datasets_status->size(); ++i) { dataset_tasks.push_back({i, datasets_status->at(i)}); } - // Run broad SQLColumns discovery in parallel: first table listing per // dataset, then table metadata retrieval. auto trace_option = TraceOptions::GetTraceOption(); @@ -455,18 +462,26 @@ StatusRecordOr> FetchBQTablesData( "MaxThreads must be configured with a positive value"}; } int max_threads = trace_option->max_threads; - auto fetch_tables_for_dataset_task = [&](DatasetTaskInput const& dataset_task) -> StatusRecordOr { + + // --- BENCHMARK START: Individual GetFilteredTables Task --- + auto start_single_listing = std::chrono::high_resolution_clock::now(); + StatusRecordOr> tables_status = GetFilteredTables(stmt_handle, catalog, dataset_task.dataset, table_pattern, kTableAndViewTypes, metadata_id); + + // --- BENCHMARK END: Individual GetFilteredTables Task --- + auto end_single_listing = std::chrono::high_resolution_clock::now(); + auto elapsed_single = std::chrono::duration_cast(end_single_listing - start_single_listing); + std::cout << "[BENCHMARK] Task: GetFilteredTables for dataset '" << dataset_task.dataset << "': " << elapsed_single.count() << " ms\n"; + if (!tables_status) { LOG(ERROR) << "FetchBQTablesData::GetFilteredTables:: " << tables_status.GetStatusRecord().message; return tables_status.GetStatusRecord(); } - DatasetTablesBatch batch{ dataset_task.dataset_index, dataset_task.dataset, {}}; batch.table_names.reserve(tables_status->size()); @@ -476,22 +491,29 @@ StatusRecordOr> FetchBQTablesData( return batch; }; + // --- BENCHMARK START: GetFilteredTables (Parallel) --- + auto start_listing_tables = std::chrono::high_resolution_clock::now(); + auto dataset_tables_results_or = ExecuteParallelTasks( max_threads, dataset_tasks, fetch_tables_for_dataset_task); + + // --- BENCHMARK END: GetFilteredTables (Parallel) --- + auto end_listing_tables = std::chrono::high_resolution_clock::now(); + auto elapsed_listing = std::chrono::duration_cast(end_listing_tables - start_listing_tables); + std::cout << "[BENCHMARK] FetchBQTablesData -> ExecuteParallelTasks(GetFilteredTables): " << elapsed_listing.count() << " ms\n"; + if (!dataset_tables_results_or) { LOG(ERROR) << "FetchBQTablesData::ExecuteParallelTasks(GetFilteredTables):: " << dataset_tables_results_or.GetStatusRecord().message; return dataset_tables_results_or.GetStatusRecord(); } - auto dataset_tables_batches = std::move(*dataset_tables_results_or); std::sort(dataset_tables_batches.begin(), dataset_tables_batches.end(), [](DatasetTablesBatch const& lhs, DatasetTablesBatch const& rhs) { return lhs.dataset_index < rhs.dataset_index; }); - std::vector table_tasks; for (auto const& dataset_batch : dataset_tables_batches) { for (auto const& table_name : dataset_batch.table_names) { @@ -499,12 +521,10 @@ StatusRecordOr> FetchBQTablesData( {table_tasks.size(), dataset_batch.dataset, table_name}); } } - struct IndexedTable { std::size_t index; Table table; }; - auto fetch_table_task = [&](TableTaskInput const& task_input) -> StatusRecordOr> { auto bq_table_status = FetchBQTableData( @@ -524,15 +544,23 @@ StatusRecordOr> FetchBQTablesData( IndexedTable{task_input.index, std::move(*bq_table_status)}); }; + // --- BENCHMARK START: FetchBQTableData (Parallel) --- + auto start_fetching_metadata = std::chrono::high_resolution_clock::now(); + auto table_results_or = ExecuteParallelTasks>( max_threads, table_tasks, fetch_table_task); + + // --- BENCHMARK END: FetchBQTableData (Parallel) --- + auto end_fetching_metadata = std::chrono::high_resolution_clock::now(); + auto elapsed_metadata = std::chrono::duration_cast(end_fetching_metadata - start_fetching_metadata); + std::cout << "[BENCHMARK] FetchBQTablesData -> ExecuteParallelTasks(FetchBQTableData): " << elapsed_metadata.count() << " ms\n"; + if (!table_results_or) { LOG(ERROR) << "FetchBQTablesData::ExecuteParallelTasks:: " << table_results_or.GetStatusRecord().message; return table_results_or.GetStatusRecord(); } - std::vector indexed_tables; std::size_t skipped_table_count = 0; indexed_tables.reserve(table_results_or->size()); @@ -547,7 +575,6 @@ StatusRecordOr> FetchBQTablesData( [](IndexedTable const& lhs, IndexedTable const& rhs) { return lhs.index < rhs.index; }); - result.reserve(indexed_tables.size()); for (auto& indexed_table : indexed_tables) { result.push_back(std::move(indexed_table.table)); diff --git a/google/cloud/odbc/bq_driver/internal/odbc_sql_tables.cc b/google/cloud/odbc/bq_driver/internal/odbc_sql_tables.cc index 2737b22b0d..1da97245fc 100644 --- a/google/cloud/odbc/bq_driver/internal/odbc_sql_tables.cc +++ b/google/cloud/odbc/bq_driver/internal/odbc_sql_tables.cc @@ -99,19 +99,39 @@ StatusRecordOr> GetFilteredDatasetIds( Options options; DatasetFilter filter; filter.all = false; + + // --- BENCHMARK START: FilterDatasets (Client Library Call) --- + auto start_client_call = std::chrono::high_resolution_clock::now(); + StatusRecordOr> datasets = bq_client.FilterDatasets(project_id, filter, options); + + // --- BENCHMARK END: FilterDatasets (Client Library Call) --- + auto end_client_call = std::chrono::high_resolution_clock::now(); + auto elapsed_client = std::chrono::duration_cast(end_client_call - start_client_call); + std::cout << "[BENCHMARK] GetFilteredDatasetIds -> bq_client.FilterDatasets: " << elapsed_client.count() << " ms\n"; + if (!datasets) { LOG(ERROR) << "GetFilteredDatasetIds::FilterDatasets:: " << datasets.GetStatusRecord().message; return datasets.GetStatusRecord(); } + + // --- BENCHMARK START: Regex Matching Loop --- + auto start_regex = std::chrono::high_resolution_clock::now(); + for (auto const& dataset : *datasets) { if ((!metadata_id && datasets_filter == "%") || std::regex_match(dataset.dataset_reference.dataset_id, filter_regex)) { dataset_ids.push_back(dataset.dataset_reference.dataset_id); } } + + // --- BENCHMARK END: Regex Matching Loop --- + auto end_regex = std::chrono::high_resolution_clock::now(); + auto elapsed_regex = std::chrono::duration_cast(end_regex - start_regex); + std::cout << "[BENCHMARK] GetFilteredDatasetIds -> Regex matching loop: " << elapsed_regex.count() << " ms\n"; + return dataset_ids; } @@ -220,20 +240,50 @@ StatusRecordOr> GetFilteredTables( std::string const& dataset_id, std::string const& tables_filter, std::string const& table_types_filter, SQLULEN metadata_id) { std::vector named_query_params; + + // --- BENCHMARK START: ProcessTableTypes --- + auto start_process_types = std::chrono::high_resolution_clock::now(); + // Normalize table type: client-library accepts type "BASE TABLE" std::string normalized_table_type_filter = ProcessTableTypes(table_types_filter); + + // --- BENCHMARK END: ProcessTableTypes --- + auto end_process_types = std::chrono::high_resolution_clock::now(); + auto elapsed_process_types = std::chrono::duration_cast(end_process_types - start_process_types); + std::cout << "[BENCHMARK] GetFilteredTables -> ProcessTableTypes: " << elapsed_process_types.count() << " ms\n"; + + + // --- BENCHMARK START: ConstructQuery --- + auto start_construct_query = std::chrono::high_resolution_clock::now(); + auto query_tables = ConstructQuery(tables_filter, normalized_table_type_filter, metadata_id, named_query_params); + + // --- BENCHMARK END: ConstructQuery --- + auto end_construct_query = std::chrono::high_resolution_clock::now(); + auto elapsed_construct_query = std::chrono::duration_cast(end_construct_query - start_construct_query); + std::cout << "[BENCHMARK] GetFilteredTables -> ConstructQuery: " << elapsed_construct_query.count() << " ms\n"; + if (!query_tables) { LOG(ERROR) << "GetFilteredTables::ConstructQuery:: " << query_tables.GetStatusRecord().message; return query_tables.GetStatusRecord(); } + + // --- BENCHMARK START: ConstructNamedParametersPostQueryRequest --- + auto start_construct_req = std::chrono::high_resolution_clock::now(); + auto post_query_request_status = ConstructNamedParametersPostQueryRequest( project_id, dataset_id, *query_tables, named_query_params); + + // --- BENCHMARK END: ConstructNamedParametersPostQueryRequest --- + auto end_construct_req = std::chrono::high_resolution_clock::now(); + auto elapsed_construct_req = std::chrono::duration_cast(end_construct_req - start_construct_req); + std::cout << "[BENCHMARK] GetFilteredTables -> ConstructNamedParametersPostQueryRequest: " << elapsed_construct_req.count() << " ms\n"; + if (!post_query_request_status) { LOG(ERROR) << "GetFilteredTables::ConstructNamedParametersPostQueryRequest:: " @@ -241,8 +291,17 @@ StatusRecordOr> GetFilteredTables( return post_query_request_status.GetStatusRecord(); } + // --- BENCHMARK START: FetchBQData --- + auto start_fetch_bq = std::chrono::high_resolution_clock::now(); + auto fetch_status_record_or = FetchBQData(stmt_handle, *post_query_request_status); + + // --- BENCHMARK END: FetchBQData --- + auto end_fetch_bq = std::chrono::high_resolution_clock::now(); + auto elapsed_fetch_bq = std::chrono::duration_cast(end_fetch_bq - start_fetch_bq); + std::cout << "[BENCHMARK] GetFilteredTables -> FetchBQData: " << elapsed_fetch_bq.count() << " ms\n"; + if (!fetch_status_record_or) { LOG(ERROR) << "GetFilteredTables::FetchBQData:: " << fetch_status_record_or.GetStatusRecord().message; diff --git a/google/cloud/odbc/bq_driver/odbc_driver_metadata.cc b/google/cloud/odbc/bq_driver/odbc_driver_metadata.cc index 1561bd9a02..158d9320df 100644 --- a/google/cloud/odbc/bq_driver/odbc_driver_metadata.cc +++ b/google/cloud/odbc/bq_driver/odbc_driver_metadata.cc @@ -538,7 +538,7 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, StatusRecord{SQLStates::k_HY013(), "Internal connection handle is null"}); } - + ConnectionHandle& conn_handle = *(handle.GetConnectionHandle()); if (!conn_handle.IsConnected()) { LOG(ERROR) << "SQLColumns:: Connection to the data source is broken"; @@ -546,19 +546,16 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, handle, StatusRecord{SQLStates::k_08S01(), "Connection to the data source is broken"}); } - std::string catalog_str; if (catalog_name_len == 0) { SQLINTEGER catalog_len = 0; SQLCHAR current_catalog[256] = {0}; conn_handle.GetAttribute(SQL_ATTR_CURRENT_CATALOG, current_catalog, sizeof(current_catalog), &catalog_len); - catalog_str.assign(reinterpret_cast(current_catalog), catalog_len); catalog_name = reinterpret_cast(catalog_str.data()); catalog_name_len = static_cast(catalog_str.size()); } - auto input_param_status = ValidateColumnParameters( catalog_name, catalog_name_len, schema_name, schema_name_len, table_name, table_name_len, column_name, column_name_len, metadata_id); @@ -579,7 +576,6 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, s_dataset_name = dsn.default_dataset; } } - // For metadata_id == SQL_TRUE, all parameters are ID Arguments. // Sanitize the ID arguments before fetching data from BQ. // For sanitization rules see: @@ -591,16 +587,24 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, SanitizeIdentifierArgument(s_table_name); SanitizeIdentifierArgument(s_column_name); } + + // --- BENCHMARK START: FetchBQTablesData --- + auto start_fetch_tables = std::chrono::high_resolution_clock::now(); // Fetch BQ Table. This particular call fetches a single table. auto filtered_tables_data_status = FetchBQTablesData( handle, s_catalog_name, s_dataset_name, s_table_name, metadata_id); + + // --- BENCHMARK END: FetchBQTablesData --- + auto end_fetch_tables = std::chrono::high_resolution_clock::now(); + auto elapsed_fetch = std::chrono::duration_cast(end_fetch_tables - start_fetch_tables); + std::cout << "[BENCHMARK] SQLColumnsInternal -> FetchBQTablesData: " << elapsed_fetch.count() << " ms\n"; + if (!filtered_tables_data_status) { LOG(ERROR) << "SQLColumns::FetchBQTablesData:: " << filtered_tables_data_status.GetStatusRecord().message; return LogAndReturnCode(handle, filtered_tables_data_status); } - // Process Table Results for each table returned from the list above. auto max_rows_status = handle.GetAttribute(SQL_MAX_ROWS); if (!max_rows_status) { @@ -609,12 +613,14 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, return LogAndReturnCode(handle, max_rows_status); } SQLULEN max_rows = *max_rows_status; - ResultSet final_result_set; + + // --- BENCHMARK START: Process Table Results Loop --- + auto start_process_tables = std::chrono::high_resolution_clock::now(); + for (auto const& bq_table : *filtered_tables_data_status) { StatusRecordOr table_result_set_status = ProcessTableResults(conn_handle, bq_table, s_column_name, metadata_id); - if (!table_result_set_status) { LOG(ERROR) << "SQLColumns::ProcessTableResults:: " << table_result_set_status.GetStatusRecord().message; @@ -625,20 +631,23 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, if (final_result_set.row_schema.empty()) { final_result_set.row_schema = table_result_set_status->row_schema; } - for (auto const& row : new_rows) { if (max_rows != 0 && final_result_set.rows.size() >= max_rows) { break; } final_result_set.rows.push_back(row); } - if (max_rows != 0 && final_result_set.rows.size() >= max_rows) { break; } } } + // --- BENCHMARK END: Process Table Results Loop --- + auto end_process_tables = std::chrono::high_resolution_clock::now(); + auto elapsed_process = std::chrono::duration_cast(end_process_tables - start_process_tables); + std::cout << "[BENCHMARK] SQLColumnsInternal -> ProcessTableResults loop: " << elapsed_process.count() << " ms\n"; + DescriptorHandle& ird = handle.GetDescriptorHandle(DescriptorType::kIRD); auto table_schema = BuildTableSchemaFromRowSchema(final_result_set.row_schema, kODBCColumnsMap); @@ -647,7 +656,6 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, << table_schema.GetStatusRecord().message; return LogAndReturnCode(handle, table_schema); } - TableReference table_fields; ird.SetConnectionHandle(&conn_handle); auto ird_status = @@ -656,14 +664,12 @@ SQLRETURN SQLColumnsInternal(SQLHSTMT stmt_handle, SQLCHAR* catalog_name, LOG(ERROR) << "SQLColumns::PopulateIrd:: " << ird_status.message; return LogAndReturnCode(handle, ird_status); } - if (!final_result_set.rows.empty()) { handle.SetResultSet(final_result_set); handle.SetStmtState(StmtStates::kStatementExecutedWithRs); } else { handle.SetStmtState(StmtStates::kStatementExecutedWithoutRs); } - return SQL_SUCCESS; } diff --git a/google/cloud/odbc/integration_tests/odbc_driver_tests/catalog_test.cc b/google/cloud/odbc/integration_tests/odbc_driver_tests/catalog_test.cc index 3a32ffe9e3..2f28ec108a 100644 --- a/google/cloud/odbc/integration_tests/odbc_driver_tests/catalog_test.cc +++ b/google/cloud/odbc/integration_tests/odbc_driver_tests/catalog_test.cc @@ -893,7 +893,51 @@ TEST(CatalogTest, SQLPrimaryKeys_TableWithPrimaryKeys) { VerifyRowWiseResults(primary_keys, single_row_expected); EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); } +TEST(CatalogTest, Benchmark_GetPrimaryKeys_ExactTable) { + auto conn = std::make_shared(); + ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS); + + // Ensure the table with primary keys exists before measuring + CreateTableDirect(conn, kTableWithPKSchema); + + auto start = std::chrono::high_resolution_clock::now(); + + RowWiseResults results = Catalog::GetPrimaryKeys( + conn, kDatasetName, kCatalogDatasetTableWithPK); + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + + std::cout << "[BENCHMARK] Catalog::GetPrimaryKeys " + << "(Exact Table with PK) : " + << elapsed.count() << " ms\n"; + + EXPECT_FALSE(results.empty()); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, Benchmark_GetPrimaryKeys_NoPKTable) { + auto conn = std::make_shared(); + ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS); + + // Ensure the table without primary keys exists before measuring + CreateTableDirect(conn, kTableWithOutPKSchema); + + auto start = std::chrono::high_resolution_clock::now(); + + RowWiseResults results = Catalog::GetPrimaryKeys( + conn, kDatasetName, kCatalogDatasetTableWithoutPK); + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + std::cout << "[BENCHMARK] Catalog::GetPrimaryKeys " + << "(Table without PK) : " + << elapsed.count() << " ms\n"; + + EXPECT_TRUE(results.empty()); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} TEST(CatalogTest, SQLPrimaryKeys_TableWithoutPrimaryKeys) { auto conn = std::make_shared(); // Create table if not exists. @@ -921,7 +965,78 @@ TEST(CatalogTest, ANSI_SQLPrimaryKeys_TableWithPrimaryKeys) { VerifyRowWiseResults(primary_keys, kCatalogPrimaryKeysExpected); EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); } +TEST(CatalogTest, Benchmark_GetForeignKeys_PkAndFkTables) { + auto conn = std::make_shared(); + ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS); + + // Ensure the relationship tables exist before measuring + CreateTableDirect(conn, kTableCustomerSchema); + CreateTableDirect(conn, kTableOrdersSchema); + + auto start = std::chrono::high_resolution_clock::now(); + + // Supplying both the primary key table and the foreign key table + RowWiseResults results = Catalog::GetForeignKeys( + conn, kDatasetName, kTableCustomer, kTableOrders); + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + + std::cout << "[BENCHMARK] Catalog::GetForeignKeys " + << "(Both PK and FK tables supplied) : " + << elapsed.count() << " ms\n"; + + EXPECT_FALSE(results.empty()); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, Benchmark_GetForeignKeys_PkTableOnly) { + auto conn = std::make_shared(); + ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS); + + CreateTableDirect(conn, kTableCustomerSchema); + CreateTableDirect(conn, kTableOrdersSchema); + + auto start = std::chrono::high_resolution_clock::now(); + + // Supplying ONLY the primary key table to find all dependent foreign keys + RowWiseResults results = Catalog::GetForeignKeys( + conn, kDatasetName, kTableCustomer, ""); + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + + std::cout << "[BENCHMARK] Catalog::GetForeignKeys " + << "(Only PK table supplied) : " + << elapsed.count() << " ms\n"; + + EXPECT_FALSE(results.empty()); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, Benchmark_GetForeignKeys_FkTableOnly) { + auto conn = std::make_shared(); + ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS); + + CreateTableDirect(conn, kTableCustomerSchema); + CreateTableDirect(conn, kTableOrdersSchema); + auto start = std::chrono::high_resolution_clock::now(); + + // Supplying ONLY the foreign key table to find the primary keys it references + RowWiseResults results = Catalog::GetForeignKeys( + conn, kDatasetName, "", kTableOrders); + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + + std::cout << "[BENCHMARK] Catalog::GetForeignKeys " + << "(Only FK table supplied) : " + << elapsed.count() << " ms\n"; + + EXPECT_FALSE(results.empty()); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} TEST(CatalogTest, ANSI_SQLPrimaryKeys_TableWithoutPrimaryKeys) { auto conn = std::make_shared(); // Create table if not exists. @@ -1805,6 +1920,323 @@ TEST(CatalogTest, SQLTables_Filter_DefaultDataset_SchemaNull) { EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); } +TEST(CatalogTest, SQLTables_Filter_DefaultDataset_SchemaNull1) { + auto conn = std::make_shared(); + std::string default_dataset = "ODBC_TEST_DATASET"; + + std::string base_conn_str = + kDefaultConnectionString + ";DefaultDataset=" + default_dataset; + + // Filter OFF + std::string conn_str_unfiltered = + base_conn_str + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str_unfiltered, conn), SQL_SUCCESS); + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, 0); + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_unfiltered = std::chrono::high_resolution_clock::now(); + + std::vector tables_unfiltered = + Catalog::GetTables(conn, kCatalogName, nullptr, nullptr, nullptr); + + auto end_unfiltered = std::chrono::high_resolution_clock::now(); + + auto elapsed_unfiltered = + std::chrono::duration_cast( + end_unfiltered - start_unfiltered); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(FilterTablesOnDefaultDataset=0) : " + << elapsed_unfiltered.count() << " ms" << std::endl; + + ASSERT_FALSE(tables_unfiltered.empty()); + + std::set ds_unfiltered; + for (auto const& t : tables_unfiltered) { + if (t.dataset_name.has_value()) { + ds_unfiltered.insert(t.dataset_name.value()); + } + } + + EXPECT_GE(ds_unfiltered.size(), 1u); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); + + // Filter ON + std::string conn_str_filtered = + base_conn_str + ";FilterTablesOnDefaultDataset=1;"; + + ASSERT_EQ(Connect(conn_str_filtered, conn), SQL_SUCCESS); + status = SQLSetStmtAttr(conn->hstmt, SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, 0); + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_filtered = std::chrono::high_resolution_clock::now(); + + std::vector tables_filtered = + Catalog::GetTables(conn, kCatalogName, nullptr, nullptr, nullptr); + + auto end_filtered = std::chrono::high_resolution_clock::now(); + + auto elapsed_filtered = + std::chrono::duration_cast( + end_filtered - start_filtered); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(FilterTablesOnDefaultDataset=1) : " + << elapsed_filtered.count() << " ms" << std::endl; + + ASSERT_FALSE(tables_filtered.empty()); + + std::set ds_filtered; + for (auto const& t : tables_filtered) { + if (t.dataset_name.has_value()) { + ds_filtered.insert(t.dataset_name.value()); + } + } + + // Only default dataset expected + EXPECT_EQ(ds_filtered.size(), 1u); + EXPECT_EQ(*ds_filtered.begin(), default_dataset); + + if (ds_unfiltered.size() > 1) { + EXPECT_LT(tables_filtered.size(), tables_unfiltered.size()); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, SQLTables_DatasetLevelEnumeration) { + auto conn = std::make_shared(); + + std::string dataset = "ODBC_TEST_DATASET"; + + // Only benchmark with FilterTablesOnDefaultDataset=0 + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_dataset_enum = + std::chrono::high_resolution_clock::now(); + + std::vector dataset_tables = + Catalog::GetTables( + conn, + kCatalogName, + dataset.c_str(), + nullptr, + nullptr); + + auto end_dataset_enum = + std::chrono::high_resolution_clock::now(); + + auto elapsed_dataset_enum = + std::chrono::duration_cast( + end_dataset_enum - start_dataset_enum); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(Dataset-Level Enumeration) : " + << elapsed_dataset_enum.count() + << " ms" << std::endl; + + ASSERT_FALSE(dataset_tables.empty()); + + // Validate all returned tables belong to requested dataset + for (auto const& table : dataset_tables) { + ASSERT_TRUE(table.dataset_name.has_value()); + EXPECT_EQ(table.dataset_name.value(), dataset); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, SQLTables_ExactTableLookup) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_name = "new_timestamp_table"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_lookup = + std::chrono::high_resolution_clock::now(); + + std::vector tables = + Catalog::GetTables( + conn, + kCatalogName, + dataset.c_str(), + table_name.c_str(), + nullptr); + + auto end_lookup = + std::chrono::high_resolution_clock::now(); + + auto elapsed_lookup = + std::chrono::duration_cast( + end_lookup - start_lookup); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(Exact Table Lookup) : " + << elapsed_lookup.count() + << " ms" << std::endl; + + ASSERT_FALSE(tables.empty()); + + // Validate returned metadata + for (auto const& table : tables) { + ASSERT_TRUE(table.dataset_name.has_value()); + ASSERT_TRUE(table.table_name.has_value()); + + EXPECT_EQ(table.dataset_name.value(), dataset); + EXPECT_EQ(table.table_name.value(), table_name); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, SQLTables_WildcardTableSearch) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_pattern = "%timestamp%"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_search = + std::chrono::high_resolution_clock::now(); + + std::vector tables = + Catalog::GetTables( + conn, + kCatalogName, + dataset.c_str(), + table_pattern.c_str(), + nullptr); + + auto end_search = + std::chrono::high_resolution_clock::now(); + + auto elapsed_search = + std::chrono::duration_cast( + end_search - start_search); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(Wildcard Table Search) : " + << elapsed_search.count() + << " ms" << std::endl; + + ASSERT_FALSE(tables.empty()); + + // Validate returned tables belong to requested dataset + for (auto const& table : tables) { + ASSERT_TRUE(table.dataset_name.has_value()); + ASSERT_TRUE(table.table_name.has_value()); + + EXPECT_EQ(table.dataset_name.value(), dataset); + + // Ensure returned table names contain "timestamp" + EXPECT_NE(table.table_name.value().find("timestamp"), + std::string::npos); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, SQLTables_FullCatalogEnumeration_WildcardCatalog) { + auto conn = std::make_shared(); + + // Power BI commonly sends CatalogName="%" + // during initial metadata discovery. + std::string catalog_pattern = "%"; + + std::string conn_str = + kDefaultConnectionString + + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_enum = + std::chrono::high_resolution_clock::now(); + + std::vector tables = + Catalog::GetTables( + conn, + catalog_pattern.c_str(), + nullptr, + nullptr, + nullptr); + + auto end_enum = + std::chrono::high_resolution_clock::now(); + + auto elapsed_enum = + std::chrono::duration_cast( + end_enum - start_enum); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(Full Catalog Enumeration using Catalog='%') : " + << elapsed_enum.count() + << " ms" << std::endl; + + ASSERT_FALSE(tables.empty()); + + // Validate that at least one dataset/schema is returned + std::set discovered_datasets; + + for (auto const& table : tables) { + if (table.dataset_name.has_value()) { + discovered_datasets.insert(table.dataset_name.value()); + } + } + + EXPECT_GE(discovered_datasets.size(), 1u); + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + #ifdef BQ_DRIVER_INTEGRATION_TESTS // This test case currently crashes with the existing ODBC Driver for BigQuery // v3.1.6.1026. The crash occurs in SQLColumns when schema_name is NULL, @@ -1812,7 +2244,8 @@ TEST(CatalogTest, SQLTables_Filter_DefaultDataset_SchemaNull) { // (e.g., 2.5.2.1004) and with our driver. We should re-enable or validate this // test case when we upgrade to a newer version of the existing driver or when // the issue is resolved. -TEST(CatalogTest, SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { +TEST(CatalogTest, + SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { // TODO(b/485172463): Enable after the performance issue is resolved auto conn = std::make_shared(); std::string default_dataset = "ODBC_TEST_DATASET"; @@ -1822,10 +2255,12 @@ TEST(CatalogTest, SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { std::string table_name = "Test Table12"; std::string quoted_table = "`" + kDatasetWithTablePrefix + table_name + "`"; + ASSERT_EQ(Connect(base_conn_str, conn), SQL_SUCCESS); Table table(quoted_table); table.Create(conn, "(id INT64, name STRING)"); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); // Filter OFF (FilterTablesOnDefaultDataset = 0) @@ -1834,38 +2269,66 @@ TEST(CatalogTest, SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { ASSERT_EQ(Connect(conn_str_unfiltered, conn), SQL_SUCCESS); - SQLRETURN status = SQLSetStmtAttr(conn->hstmt, SQL_ATTR_METADATA_ID, - (SQLPOINTER)SQL_FALSE, 0); + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + CheckError(status, "SQLSetStmtAttr (unfiltered)", conn); + auto start_unfiltered = + std::chrono::high_resolution_clock::now(); + std::vector results_unfiltered = - Catalog::GetColumns(conn, kCatalogName, + Catalog::GetColumns(conn, + kCatalogName, /*schema_name=*/nullptr, /*table_name=*/nullptr, /*column_name=*/nullptr); + auto end_unfiltered = + std::chrono::high_resolution_clock::now(); + + auto elapsed_unfiltered = + std::chrono::duration_cast( + end_unfiltered - start_unfiltered); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Full Column Metadata Enumeration - " + << "FilterTablesOnDefaultDataset=0) : " + << elapsed_unfiltered.count() + << " ms" << std::endl; + ASSERT_FALSE(results_unfiltered.empty()); std::set ds_unfiltered; + for (auto const& r : results_unfiltered) { ds_unfiltered.insert(r.dataset_name); } size_t count_unfiltered = results_unfiltered.size(); - bool has_multiple_datasets = (ds_unfiltered.size() > 1); - EXPECT_TRUE(ds_unfiltered.find(default_dataset) != ds_unfiltered.end()); + bool has_multiple_datasets = + (ds_unfiltered.size() > 1); + + EXPECT_TRUE( + ds_unfiltered.find(default_dataset) != + ds_unfiltered.end()); bool found_unfiltered = false; + for (auto const& r : results_unfiltered) { - if (r.table_name.find(table_name) != std::string::npos) { + if (r.table_name.find(table_name) != + std::string::npos) { found_unfiltered = true; break; } } EXPECT_TRUE(found_unfiltered) - << "Table with space not found in unfiltered results"; + << "Table with space not found in " + << "unfiltered results"; EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); @@ -1875,19 +2338,40 @@ TEST(CatalogTest, SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { ASSERT_EQ(Connect(conn_str_filtered, conn), SQL_SUCCESS); - status = SQLSetStmtAttr(conn->hstmt, SQL_ATTR_METADATA_ID, - (SQLPOINTER)SQL_FALSE, 0); + status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + CheckError(status, "SQLSetStmtAttr (filtered)", conn); + auto start_filtered = + std::chrono::high_resolution_clock::now(); + std::vector results_filtered = - Catalog::GetColumns(conn, kCatalogName, + Catalog::GetColumns(conn, + kCatalogName, /*schema_name=*/nullptr, /*table_name=*/nullptr, /*column_name=*/nullptr); + auto end_filtered = + std::chrono::high_resolution_clock::now(); + + auto elapsed_filtered = + std::chrono::duration_cast( + end_filtered - start_filtered); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Full Column Metadata Enumeration - " + << "FilterTablesOnDefaultDataset=1) : " + << elapsed_filtered.count() + << " ms" << std::endl; + ASSERT_FALSE(results_filtered.empty()); std::set ds_filtered; + for (auto const& r : results_filtered) { ds_filtered.insert(r.dataset_name); } @@ -1896,28 +2380,299 @@ TEST(CatalogTest, SQLColumns_Filter_DefaultDataset_SchemaNull_TableWithSpace) { EXPECT_EQ(*ds_filtered.begin(), default_dataset); if (has_multiple_datasets) { - EXPECT_LT(results_filtered.size(), count_unfiltered); + EXPECT_LT(results_filtered.size(), + count_unfiltered); } bool found_filtered = false; + for (auto const& r : results_filtered) { - if (r.table_name.find(table_name) != std::string::npos) { + if (r.table_name.find(table_name) != + std::string::npos) { found_filtered = true; break; } } EXPECT_TRUE(found_filtered) - << "Table with space not found in filtered results"; + << "Table with space not found in " + << "filtered results"; EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); - ASSERT_EQ(Connect(base_conn_str, conn), SQL_SUCCESS); + + ASSERT_EQ(Connect(base_conn_str, conn), + SQL_SUCCESS); + table.Drop(conn); + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); } - #endif // BQ_DRIVER_INTEGRATION_TESTS +TEST(CatalogTest, SQLColumns_FullMetadataFetch) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_name = "new_timestamp_table"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + ";"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + auto start = + std::chrono::high_resolution_clock::now(); + + std::vector columns = + Catalog::GetColumns( + conn, + kCatalogName, + dataset.c_str(), + table_name.c_str(), + nullptr); + + auto end = + std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast( + end - start); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Full Metadata Fetch) : " + << elapsed.count() + << " ms" << std::endl; + + ASSERT_FALSE(columns.empty()); + + for (auto const& column : columns) { + EXPECT_EQ(column.dataset_name, dataset); + EXPECT_EQ(column.table_name, table_name); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + +TEST(CatalogTest, SQLColumns_ExactColumnLookup) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_name = "new_timestamp_table"; + std::string column_name = "timestamp_col_1"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + ";"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + auto start = + std::chrono::high_resolution_clock::now(); + + std::vector columns = + Catalog::GetColumns( + conn, + kCatalogName, + dataset.c_str(), + table_name.c_str(), + column_name.c_str()); + + auto end = + std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast( + end - start); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Exact Column Lookup) : " + << elapsed.count() + << " ms" << std::endl; + + ASSERT_FALSE(columns.empty()); + + for (auto const& column : columns) { + EXPECT_EQ(column.dataset_name, dataset); + EXPECT_EQ(column.table_name, table_name); + EXPECT_EQ(column.column_name, column_name); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} +TEST(CatalogTest, SQLColumns_WildcardColumnSearch) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_name = "new_timestamp_table"; + + // Matches: + // timestamp_col_1 + // timestamp_col_2 + // ... + std::string column_pattern = "%timestamp%"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + ";"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + auto start = + std::chrono::high_resolution_clock::now(); + + std::vector columns = + Catalog::GetColumns( + conn, + kCatalogName, + dataset.c_str(), + table_name.c_str(), + column_pattern.c_str()); + + auto end = + std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast( + end - start); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Wildcard Column Search) : " + << elapsed.count() + << " ms" << std::endl; + + ASSERT_FALSE(columns.empty()); + + for (auto const& column : columns) { + EXPECT_EQ(column.dataset_name, dataset); + EXPECT_EQ(column.table_name, table_name); + + EXPECT_NE(column.column_name.find("timestamp"), + std::string::npos); + } + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} +TEST(CatalogTest, SQLColumns_LargeSchemaMetadataFetch) { + auto conn = std::make_shared(); + + std::string dataset = "kirltest"; + std::string table_name = "300_column_timestamp"; + + std::string conn_str = + kDefaultConnectionString + + ";DefaultDataset=" + dataset + ";"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + auto start = + std::chrono::high_resolution_clock::now(); + + std::vector columns = + Catalog::GetColumns( + conn, + kCatalogName, + dataset.c_str(), + table_name.c_str(), + nullptr); + + auto end = + std::chrono::high_resolution_clock::now(); + + auto elapsed = + std::chrono::duration_cast( + end - start); + + std::cout << "[BENCHMARK] Catalog::GetColumns " + << "(Large Schema Metadata Fetch) : " + << elapsed.count() + << " ms" << std::endl; + + ASSERT_FALSE(columns.empty()); + + for (auto const& column : columns) { + EXPECT_EQ(column.dataset_name, dataset); + EXPECT_EQ(column.table_name, table_name); + } + + // Validate wide schema + EXPECT_GE(columns.size(), 300); + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + + +TEST(CatalogTest, + SQLTables_FullCatalogEnumeration_TableAndViewFilter) { + auto conn = std::make_shared(); + + // Power BI commonly sends: + // CatalogName = "%" + // TableType = "TABLE,VIEW" + // during initial Navigator metadata discovery. + + std::string catalog_pattern = "%"; + std::string table_types = "TABLE,VIEW"; + + std::string conn_str = + kDefaultConnectionString + + ";FilterTablesOnDefaultDataset=0;"; + + ASSERT_EQ(Connect(conn_str, conn), SQL_SUCCESS); + + SQLRETURN status = SQLSetStmtAttr(conn->hstmt, + SQL_ATTR_METADATA_ID, + (SQLPOINTER)SQL_FALSE, + 0); + + CheckError(status, "SQLSetStmtAttr", conn); + + auto start_enum = + std::chrono::high_resolution_clock::now(); + + std::vector tables = + Catalog::GetTables( + conn, + catalog_pattern.c_str(), + nullptr, + nullptr, + table_types.c_str()); + + auto end_enum = + std::chrono::high_resolution_clock::now(); + + auto elapsed_enum = + std::chrono::duration_cast( + end_enum - start_enum); + + std::cout << "[BENCHMARK] Catalog::GetTables " + << "(Full Catalog Enumeration with TABLE/VIEW Filter) : " + << elapsed_enum.count() + << " ms" << std::endl; + + ASSERT_FALSE(tables.empty()); + + // Validate metadata + std::set discovered_datasets; + + for (auto const& table : tables) { + if (table.dataset_name.has_value()) { + discovered_datasets.insert(table.dataset_name.value()); + } + + // Validate returned object types + if (table.table_type.has_value()) { + EXPECT_TRUE( + table.table_type.value() == "TABLE" || + table.table_type.value() == "VIEW"); + } + } + + EXPECT_GE(discovered_datasets.size(), 1u); + + EXPECT_EQ(Disconnect(conn), SQL_SUCCESS); +} + TEST(SQLProcedures, TableFunction) { auto conn = std::make_shared(); std::string routine_name = kDatasetWithTablePrefix + "TEST_TABLE_ROUTINE";