Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2215,4 +2215,86 @@ TEST(CatalogTest, SQLTables_NullCatalogFiltersToCurrentProject) {
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

#ifndef BQ_DRIVER_INTEGRATION_TESTS
TEST(CatalogTest, SQLSpecialColumns_SQL_BEST_ROWID_TableWithPrimaryKeys) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithPKSchema);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_BEST_ROWID, kDatasetName, kCatalogDatasetTableWithPK);

// existing driver returns an empty result set for SQL_BEST_ROWID.
EXPECT_TRUE(special_columns.empty());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is it empty for every test case?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

found out :

What the ODBC spec asks for

SQL_BEST_ROWID: "the optimal column or set of columns that, by retrieving values from the column or columns, allows any row in the specified table to be uniquely identified" — used by applications for positioned updates/deletes and re-fetching a row.
SQL_ROWVER: columns "automatically updated by the data source when any value in the row is updated by a transaction" (SQL Server rowversion, Oracle ORA_ROWSCN, etc.).
The spec explicitly allows an empty result set when the data source has no such columns; it does not require the driver to invent one.
Why BigQuery legitimately has none

There is no ROWID/OID pseudo-column and no auto-updated row-version column, so SQL_ROWVER has nothing to return.
Primary keys in BigQuery are NOT ENFORCED . BigQuery does not guarantee uniqueness, so PK columns do not satisfy "allows any row to be uniquely identified". Advertising them as BEST_ROWID would be misleading: an app that then does UPDATE … WHERE StringField=? AND IntField=? could hit multiple rows.
BigQuery has no positioned-update/scrollable-cursor model anyway, which is the main consumer of BEST_ROWID.
So Simba's choice — return a well-formed 8-column result set with zero rows, and SQL_SUCCESS even for a nonexistent table (same as its SQLPrimaryKeys behaviour) — is the conservative, defensible implementation. A driver for an RDBMS with enforced PKs would typically derive BEST_ROWID from the PK or a unique index; that reasoning simply doesn't transfer here which is the same for our driver


EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

TEST(CatalogTest, SQLSpecialColumns_SQL_BEST_ROWID_TableWithoutPrimaryKeys) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithOutPKSchema);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_BEST_ROWID, kDatasetName, kCatalogDatasetTableWithoutPK);

EXPECT_TRUE(special_columns.empty());
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

TEST(CatalogTest, SQLSpecialColumns_SQL_ROWVER) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithPKSchema);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_ROWVER, kDatasetName, kCatalogDatasetTableWithPK);

EXPECT_TRUE(special_columns.empty());
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

TEST(CatalogTest, ANSI_SQLSpecialColumns_SQL_BEST_ROWID_TableWithPrimaryKeys) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn, true), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithPKSchema, true);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_BEST_ROWID, kDatasetName, kCatalogDatasetTableWithPK,
SQL_SCOPE_SESSION, SQL_NO_NULLS, true);

// existing driver returns an empty result set for SQL_BEST_ROWID.
EXPECT_TRUE(special_columns.empty());

EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

TEST(CatalogTest,
ANSI_SQLSpecialColumns_SQL_BEST_ROWID_TableWithoutPrimaryKeys) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn, true), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithOutPKSchema, true);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_BEST_ROWID, kDatasetName, kCatalogDatasetTableWithoutPK,
SQL_SCOPE_SESSION, SQL_NO_NULLS, true);

EXPECT_TRUE(special_columns.empty());
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

TEST(CatalogTest, ANSI_SQLSpecialColumns_SQL_ROWVER) {
auto conn = std::make_shared<ODBCHandles>();
EXPECT_EQ(Connect(kDefaultConnectionString, conn, true), SQL_SUCCESS);
CreateTableDirect(conn, kTableWithPKSchema, true);

RowWiseResults special_columns = Catalog::GetSpecialColumns(
conn, SQL_ROWVER, kDatasetName, kCatalogDatasetTableWithPK,
SQL_SCOPE_SESSION, SQL_NO_NULLS, true);

EXPECT_TRUE(special_columns.empty());
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}
#endif // BQ_DRIVER_INTEGRATION_TESTS

} // namespace google::cloud::odbc_tests
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,35 @@ TEST(StatementTest, SQLPrimaryKeys_VerifyMetadata) {
EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}

#ifndef BQ_DRIVER_INTEGRATION_TESTS
TEST(StatementTest, SQLSpecialColumns_VerifyMetadata) {
auto conn = std::make_shared<ODBCHandles>();
ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS);

SQLRETURN ret = SQLSpecialColumns(
conn->hstmt, SQL_BEST_ROWID, (SQLCHAR*)"bigquery-devtools-drivers",
SQL_NTS, (SQLCHAR*)"INTEGRATION_TESTS", SQL_NTS, (SQLCHAR*)"Test_Table",
SQL_NTS, SQL_SCOPE_SESSION, SQL_NULLABLE);
ASSERT_TRUE(SQL_SUCCEEDED(ret));

ExpectedColMetadata expected[] = {
{"SCOPE", SQL_SMALLINT, 5, 0, SQL_NULLABLE},
{"COLUMN_NAME", SQL_WVARCHAR, 128, 0, SQL_NO_NULLS},
{"DATA_TYPE", SQL_SMALLINT, 5, 0, SQL_NO_NULLS},
{"TYPE_NAME", SQL_WVARCHAR, 128, 0, SQL_NO_NULLS},
{"COLUMN_SIZE", SQL_INTEGER, 10, 0, SQL_NULLABLE},
{"BUFFER_LENGTH", SQL_INTEGER, 10, 0, SQL_NULLABLE},
{"DECIMAL_DIGITS", SQL_SMALLINT, 5, 0, SQL_NULLABLE},
{"PSEUDO_COLUMN", SQL_SMALLINT, 5, 0, SQL_NULLABLE},
};

VerifyResultSetMetadata(
conn->hstmt, static_cast<SQLSMALLINT>(std::size(expected)), expected);

EXPECT_EQ(Disconnect(conn), SQL_SUCCESS);
}
#endif // BQ_DRIVER_INTEGRATION_TESTS

TEST(StatementTest, SQLForeignKeys_VerifyMetadata) {
auto conn = std::make_shared<ODBCHandles>();
ASSERT_EQ(Connect(kDefaultConnectionString, conn), SQL_SUCCESS);
Expand Down
97 changes: 97 additions & 0 deletions google/cloud/odbc/testing/odbc_utils/catalog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,103 @@ RowWiseResults Catalog::GetPrimaryKeys(std::shared_ptr<ODBCHandles> const& conn,
return results;
}

RowWiseResults Catalog::GetSpecialColumns(
std::shared_ptr<ODBCHandles> const& conn, SQLUSMALLINT identifier_type,
std::string const& dataset, std::string const& table, SQLUSMALLINT scope,
SQLUSMALLINT nullable, bool use_ansi) {
SQLRETURN status;
int const res_cols = 8;
RowWiseResults results;

if (dataset.empty() || table.empty()) {
return results;
}

std::optional<std::string> project_id_opt = ::google::cloud::internal::GetEnv(
"CPP_BIGQUERY_ODBC_TEST_GOOGLE_CLOUD_PROJECT");
auto catalog_name =
(!project_id_opt.has_value()) ? kCatalogName : project_id_opt.value();

status = SQLSetStmtAttr(conn->hstmt, SQL_ATTR_METADATA_ID,
ToSqlPointer(SQL_FALSE), 0);
CheckError(status, "SQLSetStmtAttr", conn);

TestingDataBuffer columns[res_cols];

// 1. SCOPE (Smallint)
// 2. COLUMN_NAME (Varchar)
// 3. DATA_TYPE (Smallint)
// 4. TYPE_NAME (Varchar)
// 5. COLUMN_SIZE (Integer)
// 6. BUFFER_LENGTH (Integer)
// 7. DECIMAL_DIGITS (Smallint)
// 8. PSEUDO_COLUMN (Smallint)
for (int i = 0; i < res_cols; i++) {
if (i == 0 || i == 2 || i == 6 || i == 7) {
columns[i].target_type = SQL_C_SSHORT;
} else if (i == 4 || i == 5) {
columns[i].target_type = SQL_C_SLONG;
} else {
columns[i].target_type = SQL_C_CHAR;
}
status = SQLBindCol(conn->hstmt, static_cast<SQLUSMALLINT>(i + 1),
columns[i].target_type, columns[i].target_value,
columns[i].buffer_length, &(columns[i].str_len));
CheckError(status, "SQLBindCol", conn);
}

if (use_ansi) {
status = SQLSpecialColumnsA(
conn->hstmt, identifier_type,
const_cast<SQLCHAR*>(
reinterpret_cast<const SQLCHAR*>(catalog_name.c_str())),
static_cast<SQLSMALLINT>(catalog_name.length()),
const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(dataset.c_str())),
static_cast<SQLSMALLINT>(dataset.length()),
const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(table.c_str())),
static_cast<SQLSMALLINT>(table.length()), scope, nullable);
} else {
status = SQLSpecialColumns(
conn->hstmt, identifier_type,
const_cast<SQLCHAR*>(
reinterpret_cast<const SQLCHAR*>(catalog_name.c_str())),
static_cast<SQLSMALLINT>(catalog_name.length()),
const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(dataset.c_str())),
static_cast<SQLSMALLINT>(dataset.length()),
const_cast<SQLCHAR*>(reinterpret_cast<const SQLCHAR*>(table.c_str())),
static_cast<SQLSMALLINT>(table.length()), scope, nullable);
}
CheckError(status, "SQLSpecialColumns", conn, use_ansi);

while (true) {
Row row_result;
status = SQLFetch(conn->hstmt);
if (status == SQL_NO_DATA) {
break;
}
if (!SQL_SUCCEEDED(status)) {
CheckError(status, "SQLFetch", conn);
}
for (int i = 0; i < res_cols; i++) {
if (columns[i].str_len == SQL_NULL_DATA) {
} else if (i == 0 || i == 2 || i == 6 || i == 7) {
row_result.insert(
{i + 1, std::to_string(*reinterpret_cast<SQLSMALLINT*>(
columns[i].target_value))});
} else if (i == 4 || i == 5) {
row_result.insert({i + 1, std::to_string(*reinterpret_cast<SQLINTEGER*>(
columns[i].target_value))});
} else {
row_result.insert(
{i + 1, reinterpret_cast<char*>(columns[i].target_value)});
}
}
results.emplace_back(row_result);
}

return results;
}

RowWiseResults Catalog::GetForeignKeys(std::shared_ptr<ODBCHandles> const& conn,
std::string const& dataset,
std::string const& pk_table,
Expand Down
7 changes: 7 additions & 0 deletions google/cloud/odbc/testing/odbc_utils/catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ class Catalog {
std::string const& table = "",
bool use_ansi = false);

// Uses the SQLSpecialColumns API to fetch special columns in a dataset.
static RowWiseResults GetSpecialColumns(
std::shared_ptr<ODBCHandles> const& conn, SQLUSMALLINT identifier_type,
std::string const& dataset = "", std::string const& table = "",
SQLUSMALLINT scope = SQL_SCOPE_SESSION,
SQLUSMALLINT nullable = SQL_NO_NULLS, bool use_ansi = false);

// Uses the SQLForeignKeys API to fetch foreign keys in a dataset.
static RowWiseResults GetForeignKeys(std::shared_ptr<ODBCHandles> const& conn,
std::string const& dataset = "",
Expand Down
Loading