Skip to content

Commit

Permalink
Merge pull request #9 from maiadegraaf/SQLColumns_bug
Browse files Browse the repository at this point in the history
Alter `SQLColumns` to return SQL types instead of DuckDB types
  • Loading branch information
maiadegraaf authored Jun 26, 2024
2 parents 5168877 + ef42b58 commit 367ffbf
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 65 deletions.
2 changes: 2 additions & 0 deletions src/api_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ SQLSMALLINT ApiInfo::FindRelatedSQLType(duckdb::LogicalTypeId type_id) {
return SQL_DOUBLE;
case LogicalTypeId::LIST:
return SQL_VARCHAR;
case LogicalTypeId::BIT:
return SQL_BIT;
default:
return SQL_UNKNOWN_TYPE;
}
Expand Down
155 changes: 97 additions & 58 deletions src/common/odbc_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,64 +77,103 @@ string OdbcUtils::ParseStringFilter(const string &filter_name, const string &fil
string OdbcUtils::GetQueryDuckdbColumns(const string &catalog_filter, const string &schema_filter,
const string &table_filter, const string &column_filter) {
string sql_duckdb_columns = R"(
SELECT
NULL "TABLE_CAT",
schema_name "TABLE_SCHEM",
table_name "TABLE_NAME",
column_name "COLUMN_NAME",
data_type_id "DATA_TYPE",
data_type "TYPE_NAME",
CASE
WHEN data_type='DATE' THEN 12
WHEN data_type='TIME' THEN 15
WHEN data_type LIKE 'TIMESTAMP%' THEN 26
WHEN data_type='CHAR' OR data_type='BOOLEAN' THEN 1
WHEN data_type='VARCHAR' OR data_type='BLOB' THEN character_maximum_length
WHEN data_type LIKE '%INT%' THEN numeric_precision
WHEN data_type like 'DECIMAL%' THEN numeric_precision
WHEN data_type='FLOAT' OR data_type='DOUBLE' THEN numeric_precision
ELSE NULL
END as "COLUMN_SIZE",
CASE
WHEN data_type='DATE' THEN 4
WHEN data_type='TIME' THEN 8
WHEN data_type LIKE 'TIMESTAMP%' THEN 8
WHEN data_type='CHAR' OR data_type='BOOLEAN' THEN 1
WHEN data_type='VARCHAR' OR data_type='BLOB' THEN 16
WHEN data_type LIKE '%TINYINT' THEN 1
WHEN data_type LIKE '%SMALLINT' THEN 2
WHEN data_type LIKE '%INTEGER' THEN 4
WHEN data_type LIKE '%BIGINT' THEN 8
WHEN data_type='HUGEINT' THEN 16
WHEN data_type='FLOAT' THEN 4
WHEN data_type='DOUBLE' THEN 8
ELSE NULL
END as "BUFFER_LENGTH",
numeric_scale "DECIMAL_DIGITS",
numeric_precision_radix "NUM_PREC_RADIX",
CASE is_nullable
WHEN false THEN 0
WHEN true THEN 1
ELSE 2
END as "NULLABLE",
NULL "REMARKS",
column_default "COLUMN_DEF",
data_type_id "SQL_DATA_TYPE",
CASE
WHEN data_type='DATE' OR data_type='TIME' OR data_type LIKE 'TIMESTAMP%' THEN data_type_id
ELSE NULL
END as "SQL_DATETIME_SUB",
CASE
WHEN data_type='%CHAR' OR data_type='BLOB' THEN character_maximum_length
ELSE NULL
END as "CHAR_OCTET_LENGTH",
column_index as "ORDINAL_POSITION",
CASE is_nullable
WHEN false THEN 'NO'
WHEN true THEN 'YES'
ELSE ''
END as "IS_NULLABLE"
FROM duckdb_columns
SELECT * EXCLUDE mapping
FROM (
SELECT NULL "TABLE_CAT",
SCHEMA_NAME "TABLE_SCHEM",
TABLE_NAME "TABLE_NAME",
COLUMN_NAME "COLUMN_NAME",
MAP {
'BOOLEAN': 1, -- SQL_CHAR
'TINYINT': -6, -- SQL_TINYINT
'UTINYINT': -6, -- SQL_TINYINT
'SMALLINT': 5, -- SQL_SMALLINT
'USMALLINT': 5, -- SQL_SMALLINT
'INTEGER': 4, -- SQL_INTEGER
'UINTEGER': 4, -- SQL_INTEGER
'BIGINT': -5, -- SQL_BIGINT
'UBIGINT': -5, -- SQL_BIGINT
'FLOAT': 6, -- SQL_FLOAT
'HUGEINT': 8, -- SQL_DOUBLE
'DOUBLE': 8, -- SQL_DOUBLE
'DATE': 91, -- SQL_TYPE_DATE
'TIMESTAMP': 93, -- SQL_TYPE_TIMESTAMP
'TIME': 92, -- SQL_TYPE_TIME
'VARCHAR': 12, -- SQL_VARCHAR
'BLOB': -3, -- SQL_VARBINARY
'INTERVAL': 10, -- SQL_INTERVAL
'DECIMAL': 8, -- SQL_DOUBLE
'BIT': -7, -- SQL_BIT
'LIST': 12 -- SQL_VARCHAR
} AS mapping,
CASE
WHEN len(mapping[data_type]) != 0 THEN mapping[data_type][1]::BIGINT
ELSE data_type_id
END AS "DATA_TYPE",
data_type "TYPE_NAME",
CASE
WHEN data_type='DATE' THEN 12
WHEN data_type='TIME' THEN 15
WHEN data_type LIKE 'TIMESTAMP%' THEN 26
WHEN data_type='CHAR'
OR data_type='BOOLEAN' THEN 1
WHEN data_type='VARCHAR'
OR data_type='BLOB' THEN character_maximum_length
WHEN data_type LIKE '%INT%' THEN numeric_precision
WHEN data_type like 'DECIMAL%' THEN numeric_precision
WHEN data_type='FLOAT'
OR data_type='DOUBLE' THEN numeric_precision
ELSE NULL
END AS "COLUMN_SIZE",
CASE
WHEN data_type='DATE' THEN 4
WHEN data_type='TIME' THEN 8
WHEN data_type LIKE 'TIMESTAMP%' THEN 8
WHEN data_type='CHAR'
OR data_type='BOOLEAN' THEN 1
WHEN data_type='VARCHAR'
OR data_type='BLOB' THEN 16
WHEN data_type LIKE '%TINYINT' THEN 1
WHEN data_type LIKE '%SMALLINT' THEN 2
WHEN data_type LIKE '%INTEGER' THEN 4
WHEN data_type LIKE '%BIGINT' THEN 8
WHEN data_type='HUGEINT' THEN 16
WHEN data_type='FLOAT' THEN 4
WHEN data_type='DOUBLE' THEN 8
ELSE NULL
END AS "BUFFER_LENGTH",
numeric_scale "DECIMAL_DIGITS",
numeric_precision_radix "NUM_PREC_RADIX",
CASE is_nullable
WHEN FALSE THEN 0
WHEN TRUE THEN 1
ELSE 2
END AS "NULLABLE",
NULL "REMARKS",
column_default "COLUMN_DEF",
CASE
WHEN len(mapping[data_type]) != 0 THEN mapping[data_type][1]::BIGINT
ELSE data_type_id
END AS "SQL_DATA_TYPE",
CASE
WHEN data_type='DATE'
OR data_type='TIME'
OR data_type LIKE 'TIMESTAMP%' THEN data_type_id
ELSE NULL
END AS "SQL_DATETIME_SUB",
CASE
WHEN data_type='%CHAR'
OR data_type='BLOB' THEN character_maximum_length
ELSE NULL
END AS "CHAR_OCTET_LENGTH",
column_index AS "ORDINAL_POSITION",
CASE is_nullable
WHEN FALSE THEN 'NO'
WHEN TRUE THEN 'YES'
ELSE ''
END AS "IS_NULLABLE"
FROM duckdb_columns
)
)";

sql_duckdb_columns += " WHERE ";
Expand Down
140 changes: 133 additions & 7 deletions test/tests/catalog_functions.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "odbc_test_common.h"
#include <iostream>


#include <array>
Expand Down Expand Up @@ -204,13 +205,13 @@ static void TestSQLColumns(HSTMT &hstmt, std::map<SQLSMALLINT, SQLULEN> &types_m
}

std::vector<std::array<std::string, 4>> expected_data = {
{"bool_table", "id", "13", "INTEGER"}, {"bool_table", "t", "25", "VARCHAR"},
{"bool_table", "b", "10", "BOOLEAN"}, {"bytea_table", "id", "13", "INTEGER"},
{"bytea_table", "t", "26", "BLOB"}, {"interval_table", "id", "13", "INTEGER"},
{"interval_table", "iv", "27", "INTERVAL"}, {"interval_table", "d", "25", "VARCHAR"},
{"lo_test_table", "id", "13", "INTEGER"}, {"lo_test_table", "large_data", "26", "BLOB"},
{"test_table_1", "id", "13", "INTEGER"}, {"test_table_1", "t", "25", "VARCHAR"},
{"test_view", "id", "13", "INTEGER"}, {"test_view", "t", "25", "VARCHAR"}};
{"bool_table", "id", "4", "INTEGER"}, {"bool_table", "t", "12", "VARCHAR"},
{"bool_table", "b", "1", "BOOLEAN"}, {"bytea_table", "id", "4", "INTEGER"},
{"bytea_table", "t", "-3", "BLOB"}, {"interval_table", "id", "4", "INTEGER"},
{"interval_table", "iv", "10", "INTERVAL"}, {"interval_table", "d", "12", "VARCHAR"},
{"lo_test_table", "id", "4", "INTEGER"}, {"lo_test_table", "large_data", "-3", "BLOB"},
{"test_table_1", "id", "4", "INTEGER"}, {"test_table_1", "t", "12", "VARCHAR"},
{"test_view", "id", "4", "INTEGER"}, {"test_view", "t", "12", "VARCHAR"}};

for (int i = 0; i < expected_data.size(); i++) {
SQLRETURN ret = SQLFetch(hstmt);
Expand Down Expand Up @@ -278,3 +279,128 @@ TEST_CASE("Test Catalog Functions (SQLGetTypeInfo, SQLTables, SQLColumns, SQLGet
// Disconnect from the database
DISCONNECT_FROM_DATABASE(env, dbc);
}

// SQLColumns DATA_TYPE and SQL_DATA_TYPE should return SQL types and shoud be the same as the column type in SQLDescribeCol
TEST_CASE("Test SQLColumns DATA_TYPE and SQL_DATA_TYPE and compare to SQLDescribeCol", "[odbc]") {
SQLHANDLE env;
SQLHANDLE dbc;

HSTMT hstmt = SQL_NULL_HSTMT;

// Connect to the database using SQLConnect
CONNECT_TO_DATABASE(env, dbc);

// Allocate a statement handle
EXECUTE_AND_CHECK("SQLAllocHandle (HSTMT)", SQLAllocHandle, SQL_HANDLE_STMT, dbc, &hstmt);

std::vector<int> SQL_types = {
SQL_VARCHAR,
SQL_SMALLINT,
SQL_INTEGER,
SQL_FLOAT,
SQL_DOUBLE,
SQL_BIT,
SQL_TINYINT,
SQL_BIGINT,
SQL_VARBINARY,
SQL_TYPE_DATE,
SQL_TYPE_TIME,
};

std::string query = "CREATE TABLE test_table (";
for(int i = 0; i < SQL_types.size(); i++) {
query += "c" + std::to_string(i) + " ";
switch(SQL_types[i]) {
case SQL_VARCHAR:
query += "VARCHAR";
break;
case SQL_SMALLINT:
query += "SMALLINT";
break;
case SQL_INTEGER:
query += "INTEGER";
break;
case SQL_FLOAT:
query += "FLOAT";
break;
case SQL_DOUBLE:
query += "DOUBLE";
break;
case SQL_BIT:
query += "BIT";
break;
case SQL_TINYINT:
query += "TINYINT";
break;
case SQL_BIGINT:
query += "BIGINT";
break;
case SQL_VARBINARY:
query += "BLOB";
break;
case SQL_TYPE_DATE:
query += "DATE";
break;
case SQL_TYPE_TIME:
query += "TIME";
break;
}
query += (i == SQL_types.size() - 1) ? ")" : ", ";
}

// Create a table with different SQL types
EXECUTE_AND_CHECK("SQLExecDirect (CREATE TABLE)", SQLExecDirect, hstmt,
ConvertToSQLCHAR(query), SQL_NTS);

// Insert data into the table
EXECUTE_AND_CHECK("SQLExecDirect (INSERT INTO)", SQLExecDirect, hstmt,
ConvertToSQLCHAR("INSERT INTO test_table VALUES ('a', 1, 2, 3.14, 3.14159, true, 1, 10000000000, 'blob', '2021-01-01', '12:00:00')"), SQL_NTS);

// Call SQLColumns to get the columns of the test_table
EXECUTE_AND_CHECK("SQLColumns", SQLColumns, hstmt, nullptr, 0, ConvertToSQLCHAR("main"), SQL_NTS,
ConvertToSQLCHAR("test_table"), SQL_NTS, nullptr, 0);

// Retrieve SQL_DATA_TYPE and DATA_TYPE for each column
SQLINTEGER sql_data_type;
SQLINTEGER data_type;
SQLLEN len_or_ind_ptr;
EXECUTE_AND_CHECK("SQLBindCol", SQLBindCol, hstmt, 5, SQL_INTEGER, &data_type, sizeof(data_type), &len_or_ind_ptr);
EXECUTE_AND_CHECK("SQLBindCol", SQLBindCol, hstmt, 14, SQL_INTEGER, &sql_data_type, sizeof(sql_data_type), &len_or_ind_ptr);

// Fetch the results
for(int i = 0; i < SQL_types.size(); i++) {
SQLRETURN ret = SQLFetch(hstmt);
if (ret == SQL_SUCCESS_WITH_INFO) {
std::string state, message;
ACCESS_DIAGNOSTIC(state, message, hstmt, SQL_HANDLE_STMT);
REQUIRE(state == "07006");
REQUIRE(duckdb::StringUtil::Contains(message, "Invalid Input Error"));
ret = SQL_SUCCESS;
} else {
ODBC_CHECK(ret, "SQLFetch");
}

REQUIRE(data_type == SQL_types[i]);
REQUIRE(sql_data_type == SQL_types[i]);
}

// Use SQLDescribeCol to assert that the data type is the same for each column
// Select * from the table
EXECUTE_AND_CHECK("SQLExecDirect (SELECT *)", SQLExecDirect, hstmt, ConvertToSQLCHAR("SELECT * FROM test_table"), SQL_NTS);

// Fetch the results
EXECUTE_AND_CHECK("SQLFetch", SQLFetch, hstmt);

// Check the data types of the columns using METADATA_CHECK which calls SQLDescribeCol
for(int i = 0; i < SQL_types.size(); i++) {
std::string col_name = "c" + std::to_string(i);
METADATA_CHECK(hstmt, i + 1, col_name, col_name.size(), SQL_types[i], 0, 0, SQL_NULLABLE_UNKNOWN);
}

// Free the statement handle
EXECUTE_AND_CHECK("SQLFreeStmt (HSTMT)", SQLFreeStmt, hstmt, SQL_CLOSE);
EXECUTE_AND_CHECK("SQLFreeHandle (HSTMT)", SQLFreeHandle, SQL_HANDLE_STMT, hstmt);

// Disconnect from the database
DISCONNECT_FROM_DATABASE(env, dbc);
}

0 comments on commit 367ffbf

Please sign in to comment.