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
71 changes: 66 additions & 5 deletions driver/api/odbc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,11 @@ SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLTables)(
query << ")";
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this a simplified version of the constraint above (lines 911–917)?

if (is_pattern && !is_odbc_v2) {
    if (!isMatchAnythingCatalogFnPatternArg(catalog))
        query << " AND isNotNull(TABLE_CAT) AND coalesce(TABLE_CAT, '') LIKE '" << escapeForSQL(catalog) << "'";
}
else if (CatalogName) {
    query << " AND isNotNull(TABLE_CAT) AND coalesce(TABLE_CAT, '') == '" << escapeForSQL(catalog) << "'";
}

I understand that this filter didn’t work for you. Could you please provide an example where you encountered a problem?
I believe AND database = {catalog} does not always work, because the catalog name can be a pattern (e.g., %).

if (!catalog.empty() && catalog != "%") {
query << " AND database = '" << escapeForSQL(catalog) << "'";
}

}

query << " ORDER BY TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, TABLE_NAME";
Expand Down Expand Up @@ -1266,8 +1271,8 @@ SQLRETURN SQL_API EXPORTED_FUNCTION(SQLGetFunctions)(HDBC connection_handle, SQL
SET_EXISTS(SQL_API_SQLSETENVATTR);
//SET_EXISTS(SQL_API_SQLSETPOS);
SET_EXISTS(SQL_API_SQLSETSTMTATTR);
//SET_EXISTS(SQL_API_SQLSPECIALCOLUMNS);
//SET_EXISTS(SQL_API_SQLSTATISTICS);
SET_EXISTS(SQL_API_SQLSPECIALCOLUMNS);
SET_EXISTS(SQL_API_SQLSTATISTICS);
//SET_EXISTS(SQL_API_SQLTABLEPRIVILEGES);
SET_EXISTS(SQL_API_SQLTABLES);

Expand Down Expand Up @@ -1323,8 +1328,23 @@ SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLSpecialColumns)(HSTMT StatementHa
SQLSMALLINT NameLength3,
SQLUSMALLINT Scope,
SQLUSMALLINT Nullable) {
LOG(__FUNCTION__);
return SQL_ERROR;
std::stringstream query;
query << "SELECT "
"cast(NULL, 'Nullable(Int16)') AS SCOPE, "
"cast('', 'String') AS COLUMN_NAME, "
"cast(0, 'Int16') AS DATA_TYPE, "
"cast('', 'String') AS TYPE_NAME, "
"cast(NULL, 'Nullable(Int32)') AS COLUMN_SIZE, "
"cast(NULL, 'Nullable(Int32)') AS BUFFER_LENGTH, "
"cast(NULL, 'Nullable(Int16)') AS DECIMAL_DIGITS, "
"cast(NULL, 'Nullable(Int16)') AS PSEUDO_COLUMN "
"WHERE (1 == 0)";
auto func = [&](Statement & statement) {
LOG(__FUNCTION__);
statement.executeQuery(query.str());
return SQL_SUCCESS;
};
return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func);
}

SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLStatistics)(HSTMT StatementHandle,
Expand All @@ -1337,7 +1357,48 @@ SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLStatistics)(HSTMT StatementHandle
SQLUSMALLINT Unique,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we return nothing if Unique is set to SQL_INDEX_UNIQUE? Data skipping indexes do not guarantee uniqueness.

SQLUSMALLINT Reserved) {
LOG(__FUNCTION__);
return SQL_ERROR;

auto func = [&](Statement & statement) {
const auto catalog = (CatalogName ? toUTF8(CatalogName, NameLength1) : statement.getParent().database);
const auto schema = (SchemaName ? toUTF8(SchemaName, NameLength2) : "");
const auto table = (TableName ? toUTF8(TableName, NameLength3) : "");

// Build query to retrieve index/statistics information
// ClickHouse has indices which we can report as statistics
std::stringstream query;
query << "SELECT "
"cast(database, 'Nullable(String)') AS TABLE_CAT, "
"cast('', 'Nullable(String)') AS TABLE_SCHEM, "
"cast(table, 'String') AS TABLE_NAME, "
"cast(" + toSqlQueryValue(SQL_FALSE) + ", 'Int16') AS NON_UNIQUE, "
"cast(NULL, 'Nullable(String)') AS INDEX_QUALIFIER, "
"cast(name, 'Nullable(String)') AS INDEX_NAME, "
"cast(" + toSqlQueryValue(SQL_INDEX_OTHER) + ", 'Int16') AS TYPE, "
"cast(1, 'Int16') AS ORDINAL_POSITION, "
"cast(NULL, 'Nullable(String)') AS COLUMN_NAME, "
"cast(NULL, 'Nullable(String)') AS ASC_OR_DESC, "
"cast(NULL, 'Nullable(Int32)') AS CARDINALITY, "
"cast(NULL, 'Nullable(Int32)') AS PAGES, "
"cast(NULL, 'Nullable(String)') AS FILTER_CONDITION "
"FROM system.data_skipping_indices "
"WHERE (1 == 1)";

// Add filters based on provided parameters
if (!catalog.empty() && catalog != "%") {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the catalog != "%" check is unnecessary here. According to the documentation for the SQLStatistics function:

CatalogName cannot contain a string search pattern.

The same restriction applies to schemas and tables as well.

query << " AND database = '" << escapeForSQL(catalog) << "'";
}

if (!table.empty() && table != "%") {
query << " AND table = '" << escapeForSQL(table) << "'";
}

query << " ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION";
statement.executeQuery(query.str());

return SQL_SUCCESS;
};

return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func);
Comment on lines +1360 to +1401
Copy link
Contributor

Choose a reason for hiding this comment

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

This is how I would implement a minimum working version of this function. I still need to understand what we should show in PAGES; for now, I took the size of the table and divided it by 4096. A better version of this function would also return data for the columns used to sort the data, as well as any other indices. However, this minimal version already makes MS Access work.

Suggested change
auto func = [&](Statement & statement) {
const auto catalog = (CatalogName ? toUTF8(CatalogName, NameLength1) : statement.getParent().database);
const auto schema = (SchemaName ? toUTF8(SchemaName, NameLength2) : "");
const auto table = (TableName ? toUTF8(TableName, NameLength3) : "");
// Build query to retrieve index/statistics information
// ClickHouse has indices which we can report as statistics
std::stringstream query;
query << "SELECT "
"cast(database, 'Nullable(String)') AS TABLE_CAT, "
"cast('', 'Nullable(String)') AS TABLE_SCHEM, "
"cast(table, 'String') AS TABLE_NAME, "
"cast(" + toSqlQueryValue(SQL_FALSE) + ", 'Int16') AS NON_UNIQUE, "
"cast(NULL, 'Nullable(String)') AS INDEX_QUALIFIER, "
"cast(name, 'Nullable(String)') AS INDEX_NAME, "
"cast(" + toSqlQueryValue(SQL_INDEX_OTHER) + ", 'Int16') AS TYPE, "
"cast(1, 'Int16') AS ORDINAL_POSITION, "
"cast(NULL, 'Nullable(String)') AS COLUMN_NAME, "
"cast(NULL, 'Nullable(String)') AS ASC_OR_DESC, "
"cast(NULL, 'Nullable(Int32)') AS CARDINALITY, "
"cast(NULL, 'Nullable(Int32)') AS PAGES, "
"cast(NULL, 'Nullable(String)') AS FILTER_CONDITION "
"FROM system.data_skipping_indices "
"WHERE (1 == 1)";
// Add filters based on provided parameters
if (!catalog.empty() && catalog != "%") {
query << " AND database = '" << escapeForSQL(catalog) << "'";
}
if (!table.empty() && table != "%") {
query << " AND table = '" << escapeForSQL(table) << "'";
}
query << " ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION";
statement.executeQuery(query.str());
return SQL_SUCCESS;
};
return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func);
auto func = [&](Statement & statement) {
const auto catalog = (CatalogName ? toUTF8(CatalogName, NameLength1) : statement.getParent().database);
const auto schema = (SchemaName ? toUTF8(SchemaName, NameLength2) : "");
const auto table = (TableName ? toUTF8(TableName, NameLength3) : "");
// The driver only provides SQL_TABLE_STAT data, at the moment no information about the indices
std::stringstream query;
query << "SELECT "
"cast(database, 'Nullable(String)') AS TABLE_CAT, "
"cast(NULL, 'Nullable(String)') AS TABLE_SCHEM, "
"cast(name, 'String') AS TABLE_NAME, "
"cast(NULL, 'Nullable(Int16)') AS NON_UNIQUE, "
"cast(NULL, 'Nullable(String)') AS INDEX_QUALIFIER, "
"cast(NULL, 'Nullable(String)') AS INDEX_NAME, "
"cast(" + toSqlQueryValue(SQL_TABLE_STAT) + ", 'Int16') AS TYPE, "
"cast(NULL, 'Nullable(Int16)') AS ORDINAL_POSITION, "
"cast(NULL, 'Nullable(String)') AS COLUMN_NAME, "
"cast(NULL, 'Nullable(String)') AS ASC_OR_DESC, "
"cast(total_rows, 'Nullable(Int32)') AS CARDINALITY, "
"cast(ceil(total_bytes / 4096), 'Nullable(Int32)') AS PAGES, "
"cast(NULL, 'Nullable(String)') AS FILTER_CONDITION "
"FROM system.tables "
"WHERE name = " + toSqlQueryValue(table) + " AND database = " + toSqlQueryValue(catalog);
statement.executeQuery(query.str());
return SQL_SUCCESS;
};
return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func);

}

SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLColumnPrivileges)(HSTMT hstmt,
Expand Down
1 change: 1 addition & 0 deletions driver/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ function (declare_odbc_test_targets libname UNICODE)
performance_it.cpp
type_info_it.cpp
authentication_it.cpp
catalog_functions_it.cpp
)

if (CH_ODBC_ENABLE_CODE_COVERAGE)
Expand Down
Loading
Loading