Skip to content

Commit

Permalink
ODBC-391 Only the fix as lower_case_table_names can't be changed for
Browse files Browse the repository at this point in the history
session.
i.e. the test has been added, but to server has to be in
lower_case_table_names=2 mode to test the fix, otherwise it ensures that
the fix doesn't break catalog functions against server in "normaler"
mode.
The fix adds reading of the lower_case_table_names, and if it's 2, then
it compares table name case insensitively where case sensitive
comparison would be used otherwise.
The problem actually affects not ony SQLStatistics, but almost all
catalog functions, that take catalog and/or table parameter as ordinary
argument.
  • Loading branch information
lawrinn committed Jun 10, 2023
1 parent 60a5bd6 commit a210bfa
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 25 deletions.
49 changes: 44 additions & 5 deletions ma_catalog.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,59 @@ static int AddPvCondition(MADB_Dbc *dbc, void* buffer, size_t bufferLen, char* v
}
/* }}} */

/* {{{ Read_lower_case_table_names */
static char Read_lower_case_table_names(MADB_Dbc *Dbc)
{
if (Dbc->lcTableNamesMode2 < 0)
{
MYSQL_RES *res;
MYSQL_ROW row;

if (mysql_real_query(Dbc->mariadb, "SELECT @@lower_case_table_names", sizeof("SELECT @@lower_case_table_names") - 1))
{
Dbc->lcTableNamesMode2= '\0';
return Dbc->lcTableNamesMode2;
}
res= mysql_store_result(Dbc->mariadb);
row= mysql_fetch_row(res);
if (row[0][0] == '2')
{
Dbc->lcTableNamesMode2= '\1';
}
else
{
Dbc->lcTableNamesMode2= '\0';
}
mysql_free_result(res);
}
return Dbc->lcTableNamesMode2;
}
/* }}} */

/* {{{ AddOaCondition */
static int AddOaCondition(MADB_Dbc *dbc, void* buffer, size_t bufferLen, char* value, SQLSMALLINT len)
static int AddOaCondition(MADB_Dbc *Dbc, void* buffer, size_t bufferLen, char* value, SQLSMALLINT len)
{
char escaped[2 * NAME_LEN + 1];
const char *cs= "=BINARY'", *ci= "='", *compare= cs;
const size_t cs_len= sizeof("=BINARY'") - 1, ci_len= sizeof("='") - 1;
size_t compare_len= cs_len;
if (len < 0)
{
len = (SQLSMALLINT)strlen(value);
len= (SQLSMALLINT)strlen(value);
}

len = (SQLSMALLINT)mysql_real_escape_string(dbc->mariadb, escaped, value, len);
len= (SQLSMALLINT)mysql_real_escape_string(Dbc->mariadb, escaped, value, len);

if (Read_lower_case_table_names(Dbc))
{
compare= ci;
compare_len= ci_len;
}

/* If DynString */
if (bufferLen == (size_t)-1)
{
if (MADB_DYNAPPENDCONST((MADB_DynString*)buffer, " = BINARY '") ||
if (MADB_DynstrAppendMem((MADB_DynString*)buffer, compare, compare_len) ||
MADB_DynstrAppendMem((MADB_DynString*)buffer, escaped, len) ||
MADB_DynstrAppendMem((MADB_DynString*)buffer, "' ", 2))
{
Expand All @@ -76,7 +115,7 @@ static int AddOaCondition(MADB_Dbc *dbc, void* buffer, size_t bufferLen, char* v
return 0;
}

return _snprintf((char*)buffer, bufferLen, " = BINARY '%.*s' ", len, escaped);
return _snprintf((char*)buffer, bufferLen, "%s%.*s' ", compare, len, escaped);
}
/* }}} */

Expand Down
3 changes: 2 additions & 1 deletion ma_connection.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/************************************************************************************
Copyright (C) 2013,2021 MariaDB Corporation AB
Copyright (C) 2013,2023 MariaDB Corporation AB
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
Expand Down Expand Up @@ -481,6 +481,7 @@ MADB_Dbc *MADB_DbcInit(MADB_Env *Env)
goto cleanup;

Connection->AutoCommit= 4;
Connection->lcTableNamesMode2= (char)-1; /* -1 means we don't know if lower_case_table_names=2, ie that info has never been requested yet */
Connection->Environment= Env;
Connection->Methods= &MADB_Dbc_Methods;
//CopyClientCharset(&SourceAnsiCs, &Connection->Charset);
Expand Down
1 change: 1 addition & 0 deletions ma_odbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ struct st_ma_odbc_connection
char ServerCapabilities;
my_bool IsAnsi;
my_bool IsMySQL;
char lcTableNamesMode2;
};

typedef BOOL (__stdcall *PromptDSN)(HWND hwnd, MADB_Dsn *Dsn);
Expand Down
146 changes: 129 additions & 17 deletions test/catalog2.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved.
2017, 2022 MariaDB Corporation AB
2017, 2023 MariaDB Corporation AB
The MySQL Connector/ODBC is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
Expand Down Expand Up @@ -755,7 +755,7 @@ ODBC_TEST(t_bug57182)
SQLLEN nRowCount;
SQLCHAR buff[24];

OK_SIMPLE_STMT(Stmt, "drop procedure if exists bug57182");
OK_SIMPLE_STMT(Stmt, "DROP PROCEDURE IF EXISTS bug57182");
if (!SQL_SUCCEEDED(SQLExecDirect(Stmt, "CREATE DEFINER=`adb`@`%` PROCEDURE `bug57182`(in id int, in name varchar(20)) "
"BEGIN"
" insert into simp values (id, name);"
Expand Down Expand Up @@ -839,9 +839,9 @@ ODBC_TEST(t_bug55870)
SQLHDBC hdbc1;
SQLHSTMT hstmt1;

OK_SIMPLE_STMT(Stmt, "drop table if exists bug55870r");
OK_SIMPLE_STMT(Stmt, "drop table if exists bug55870_2");
OK_SIMPLE_STMT(Stmt, "drop table if exists bug55870");
OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS bug55870r");
OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS bug55870_2");
OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS bug55870");
OK_SIMPLE_STMT(Stmt, "create table bug55870(a int not null primary key, "
"b varchar(20) not null, c varchar(100) not null, INDEX(b)) ENGINE=InnoDB");

Expand All @@ -850,11 +850,11 @@ ODBC_TEST(t_bug55870)
Thus let's test it on NO_I_S connection too */
CHECK_ENV_RC(Env, SQLAllocConnect(Env, &hdbc1));

sprintf((char *)noI_SconnStr, "DSN=%s;UID=%s;PWD=%s; NO_I_S=1", my_dsn, my_uid, my_pwd);
sprintf((char *)noI_SconnStr, "DSN=%s;UID=%s;PWD=%s;NO_I_S=1", my_dsn, my_uid, my_pwd);

sprintf(query, "grant Insert, Select on bug55870 to %s", my_uid);
sprintf(query, "GRANT Insert, Select ON bug55870 TO %s", my_uid);
SQLExecDirect(Stmt, query, SQL_NTS);
sprintf(query, "grant Insert (c), Select (c), Update (c) on bug55870 to %s", my_uid);
sprintf(query, "GRANT Insert (c), Select (c), Update (c) ON bug55870 to %s", my_uid);
SQLExecDirect(Stmt, query, SQL_NTS);

CHECK_DBC_RC(hdbc1, SQLDriverConnect(hdbc1, NULL, noI_SconnStr, sizeof(noI_SconnStr), NULL,
Expand Down Expand Up @@ -1332,14 +1332,21 @@ ODBC_TEST(odbc119)
{
SQLCHAR StrValue[32];
SQLLEN RowCount;

OK_SIMPLE_STMT(Stmt, "drop table if exists t_odbc119");
OK_SIMPLE_STMT(Stmt, "create table t_odbc119(id int not null primary key auto_increment, "
"ui_p1 INT NOT NULL, iu_p2 INT NOT NULL, a varchar(100) not null, KEY `INDEX` (`a`), UNIQUE KEY `UNIQUE` (ui_p1, iu_p2)) ENGINE=InnoDB");

CHECK_STMT_RC(Stmt, SQLStatistics(Stmt, NULL, SQL_NTS, NULL, SQL_NTS,
"t_odbc119", SQL_NTS,
SQL_INDEX_ALL, SQL_QUICK));
#ifdef _WIN32
/* Whether lower_case_table_names is 1 or 2 - it should work with lower case name. if 1 - it is created in lowercase, if 2
* - it is still compared in lowercase.
*/
SQLCHAR *tname= (SQLCHAR*)"t_odbc119";
#else
SQLCHAR *tname= (SQLCHAR*)"t_Odbc119";
#endif
/* Made the name not all lower case to address ODBC-391/370(if lower_case_table_names=2). However 391 has got its own testcase */
OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_Odbc119");
OK_SIMPLE_STMT(Stmt, "CREATE TABLE t_Odbc119(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"ui_p1 INT NOT NULL, iu_p2 INT NOT NULL, a VARCHAR(100) NOT NULL, KEY `INDEX` (`a`), UNIQUE KEY `UNIQUE` (ui_p1, iu_p2)) ENGINE=InnoDB");

/* Here it probably can fail in case of case-insentive FS on MacOS and lower_case_table_names=1(not sure if it's even possible) */
CHECK_STMT_RC(Stmt, SQLStatistics(Stmt, NULL, SQL_NTS, NULL, SQL_NTS, tname, SQL_NTS, SQL_INDEX_ALL, SQL_QUICK));
CHECK_STMT_RC(Stmt, SQLRowCount(Stmt, &RowCount));
is_num(RowCount, 4);

Expand Down Expand Up @@ -1373,7 +1380,7 @@ ODBC_TEST(odbc119)

CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

OK_SIMPLE_STMT(Stmt, "DROP TABLE t_odbc119");
OK_SIMPLE_STMT(Stmt, "DROP TABLE t_Odbc119");

return OK;
}
Expand Down Expand Up @@ -1865,6 +1872,110 @@ ODBC_TEST(odbc361)
return OK;
}

/* With lower_case_table_names=2 server the driver may not read indexes in SQLStatistics
* We can't assure that lower_case_table_names=2, but it's a good testcase for other modes as well.
* Besides other catalog functions are affected - not only SQLStatistics
* The problem arose, when application read (db or) table name with SQLTables, and it has original letter cases,
* but then if to use ifas parameter to (catalog or) table argument for various vatalog function, the server compares
* it to locase values of (db or) table name, and it failed(since ordinary argument comparison has to be cace-sensitive.
* The test basically makes sure, that this works
*/
ODBC_TEST(odbc391)
{
SQLCHAR tname[12], buffer[MAX_NAME_LEN], dbname[MAX_NAME_LEN], pname[12];
SQLLEN dbnameLen, tnameLen, pnameLen;
SQLINTEGER len;
BOOL found= FALSE;
SQLCHAR dropUser[24 + sizeof(my_host)], createUser[52 + sizeof(my_host)], grantAll[40 + sizeof(my_host)], revokeSelect[48 + sizeof(my_host)];

OK_SIMPLE_STMT(Stmt, "DROP SCHEMA IF EXISTS _SchemaOdbc391");
OK_SIMPLE_STMT(Stmt, "CREATE SCHEMA _SchemaOdbc391");
CHECK_DBC_RC(Connection, SQLGetConnectAttr(Connection, SQL_ATTR_CURRENT_CATALOG, buffer, sizeof(buffer), &len));
CHECK_DBC_RC(Connection, SQLSetConnectAttr(Connection, SQL_ATTR_CURRENT_CATALOG, (SQLPOINTER)"_SchemaOdbc391", 14));
CHECK_STMT_RC(Stmt, SQLTables(Stmt, (SQLCHAR*)SQL_ALL_CATALOGS, 1, "", 0, "", 0, NULL, 0));
while (SQLFetch(Stmt) != SQL_NO_DATA)
{
CHECK_STMT_RC(Stmt, SQLGetData(Stmt, 1, SQL_CHAR, dbname, sizeof(dbname), &dbnameLen));
if (dbnameLen == (sizeof("_SchemaOdbc391") - 1) && strcmp(dbname + 8, "dbc391") == 0)
{
found= TRUE;
break;
}
}
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));
FAIL_IF(!found, "Created schema wasn't found");

OK_SIMPLE_STMT(Stmt, "DROP TABLE IF EXISTS t_Odbc391");
/* Current_Timestamp() is intentionally in mixed case, however there was no problem with that */
OK_SIMPLE_STMT(Stmt, "CREATE TABLE t_Odbc391(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"ts TIMESTAMP on update Current_Timestamp(), a VARCHAR(100) NOT NULL) ENGINE=InnoDB");

CHECK_STMT_RC(Stmt, SQLTables(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0,
NULL, 0, NULL, 0));
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
CHECK_STMT_RC(Stmt, SQLGetData(Stmt, 3, SQL_CHAR, tname, sizeof(tname), &tnameLen));
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

CHECK_STMT_RC(Stmt, SQLColumns(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0,
tname, (SQLSMALLINT)tnameLen, NULL, 0));
is_num(3, my_print_non_format_result(Stmt));
/* my_print_non_format_result closes cursor */

CHECK_STMT_RC(Stmt, SQLStatistics(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, SQL_NTS, tname, (SQLSMALLINT)tnameLen,
SQL_INDEX_ALL, SQL_QUICK));
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
EXPECT_STMT(Stmt, SQLFetch(Stmt), SQL_NO_DATA);
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

CHECK_STMT_RC(Stmt, SQLSpecialColumns(Stmt, SQL_ROWVER, dbname, (SQLSMALLINT)dbnameLen, NULL, SQL_NTS, tname, (SQLSMALLINT)tnameLen,
SQL_SCOPE_TRANSACTION, SQL_NULLABLE));
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
EXPECT_STMT(Stmt, SQLFetch(Stmt), SQL_NO_DATA);
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

CHECK_STMT_RC(Stmt, SQLPrimaryKeys(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, SQL_NTS, tname, (SQLSMALLINT)tnameLen));
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
EXPECT_STMT(Stmt, SQLFetch(Stmt), SQL_NO_DATA);
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

OK_SIMPLE_STMT(Stmt, "CREATE PROCEDURE ProcOdbc391(IN Id INT, IN Name VARCHAR(20))"\
"BEGIN END;"
);
CHECK_STMT_RC(Stmt, SQLProcedures(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0,
NULL, 0));
CHECK_STMT_RC(Stmt, SQLFetch(Stmt));
CHECK_STMT_RC(Stmt, SQLGetData(Stmt, 3, SQL_CHAR, pname, sizeof(pname), &pnameLen));
EXPECT_STMT(Stmt, SQLFetch(Stmt), SQL_NO_DATA);
CHECK_STMT_RC(Stmt, SQLFreeStmt(Stmt, SQL_CLOSE));

CHECK_STMT_RC(Stmt, SQLProcedureColumns(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0,
pname, (SQLSMALLINT)pnameLen, NULL, 0));
is_num(2, my_print_non_format_result(Stmt));
/* my_print_non_format_result closes cursor */

_snprintf(dropUser, sizeof(dropUser), "DROP USER Odbc391@'%s'", my_host);
/* we can have error, if there is simply no such user, and that is supposed to be the case, actually */
SQLExecDirect(Stmt, dropUser, SQL_NTS);
_snprintf(createUser, sizeof(createUser), "CREATE USER Odbc391@'%s' IDENTIFIED BY 's3CureP@wd'", my_host);
CHECK_USER_OPERATION(Stmt, createUser);
_snprintf(grantAll, sizeof(grantAll), "GRANT SELECT ON t_Odbc391 TO Odbc391@'%s'", my_host);
CHECK_USER_OPERATIONX(Stmt, grantAll, dropUser);
_snprintf(grantAll, sizeof(grantAll), "GRANT SELECT(id,ts,a) ON t_Odbc391 TO Odbc391@'%s'", my_host);
CHECK_USER_OPERATIONX(Stmt, grantAll, dropUser);
_snprintf(revokeSelect, sizeof(revokeSelect), "REVOKE SELECT(ts) ON t_Odbc391 FROM Odbc391@'%s'", my_host);
CHECK_USER_OPERATIONX(Stmt, revokeSelect, dropUser);

CHECK_STMT_RC(Stmt, SQLTablePrivileges(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0, tname, (SQLSMALLINT)tnameLen));
is_num(1, my_print_non_format_result(Stmt));
CHECK_STMT_RC(Stmt, SQLColumnPrivileges(Stmt, dbname, (SQLSMALLINT)dbnameLen, NULL, 0, tname, (SQLSMALLINT)tnameLen, NULL, 0));
is_num(2, my_print_non_format_result(Stmt));

OK_SIMPLE_STMT(Stmt, dropUser);
OK_SIMPLE_STMT(Stmt, "DROP SCHEMA _SchemaOdbc391");
CHECK_DBC_RC(Connection, SQLSetConnectAttr(Connection, SQL_ATTR_CURRENT_CATALOG, buffer, len));
return OK;
}


MA_ODBC_TESTS my_tests[]=
{
Expand Down Expand Up @@ -1895,6 +2006,7 @@ MA_ODBC_TESTS my_tests[]=
{odbc316, "odbc316_empty_string_parameters", NORMAL},
{odbc324, "odbc324_sqltables_versioned_table",NORMAL},
{odbc361, "odbc361_unique_with_nulls", NORMAL},
{odbc391, "odbc391_mixed_case_names", NORMAL},
{NULL, NULL}
};

Expand Down
15 changes: 13 additions & 2 deletions test/tap.h
Original file line number Diff line number Diff line change
Expand Up @@ -757,8 +757,8 @@ int check_sqlstate_ex(SQLHANDLE hnd, SQLSMALLINT hndtype, char *sqlstate)

return OK;
}
#define check_sqlstate(stmt, sqlstate) \
check_sqlstate_ex((stmt), SQL_HANDLE_STMT, (sqlstate))
#define check_sqlstate(_STMT, _SQLSTATE) \
check_sqlstate_ex((_STMT), SQL_HANDLE_STMT, (_SQLSTATE))

#define CHECK_SQLSTATE_EX(A,B,C) FAIL_IF(check_sqlstate_ex(A,B,C) != OK, "Unexpected sqlstate!")
#define CHECK_SQLSTATE(A,C) CHECK_SQLSTATE_EX(A, SQL_HANDLE_STMT, C)
Expand All @@ -776,6 +776,17 @@ int get_native_errcode(SQLHSTMT Stmt)
return err_code;
}

#define SKIP_IF_NOT_GRANTED(_STMT) odbc_print_error(SQL_HANDLE_STMT, _STMT);\
if (get_native_errcode(_STMT) == 1142) skip("Test user doesn't have enough privileges to run this test")
#define SKIP_IF_NOT_GRANTED_OR_ERROR(_STMT) SKIP_IF_NOT_GRANTED(_STMT);\
return FAIL

#define CHECK_USER_OPERATION(_STMT, _QUERY) do { if (!SQL_SUCCEEDED(SQLExecDirect(_STMT, _QUERY, SQL_NTS))) { SKIP_IF_NOT_GRANTED_OR_ERROR(_STMT); } } while (0)
#define CHECK_USER_OPERATIONX(_STMT, _QUERY, _QUERYONFAILURE) do { if (!SQL_SUCCEEDED(SQLExecDirect(_STMT, _QUERY, SQL_NTS))) {\
SQLExecDirect(_STMT, _QUERYONFAILURE, SQL_NTS);\
SKIP_IF_NOT_GRANTED(_STMT);\
return FAIL;\
} } while (0)

int using_dm(HDBC hdbc)
{
Expand Down

0 comments on commit a210bfa

Please sign in to comment.