Description:
SQLForeignKeys crash with 19 or more foreign keys
How to repeat:
Create table with many foreign keys (>= 19).
Function SQLForeignKeys use the value stmt->result->field_count, which is always 18 from mysql_table_status, for size of stmt->result_array. If now many foreign keys (>=19) are written in the stmt->result_array the driver crash.
Suggested fix:
It is not easy to set the correct size for stmt->result_array.
I duplicate the while loop, the first one counts, the second one set the values.
Here the code:
SQLRETURN SQL_API SQLForeignKeys(SQLHSTMT hstmt,
SQLCHAR FAR *szPkTableQualifier,
SQLSMALLINT cbPkTableQualifier,
SQLCHAR FAR *szPkTableOwner,
SQLSMALLINT cbPkTableOwner,
SQLCHAR FAR *szPkTableName,
SQLSMALLINT cbPkTableName,
SQLCHAR FAR *szFkTableQualifier,
SQLSMALLINT cbFkTableQualifier,
SQLCHAR FAR *szFkTableOwner,
SQLSMALLINT cbFkTableOwner,
SQLCHAR FAR *szFkTableName,
SQLSMALLINT cbFkTableName)
{
STMT FAR *stmt=(STMT FAR*) hstmt;
uint row_count= 0;
DBUG_ENTER("SQLForeignKeys");
DBUG_PRINT("enter",("PKQualifier: '%s'(%d) PKOwner: '%s'(%d) PKTable: '%s'(%d) \
FKQualifier: '%s'(%d) FKOwner: '%s'(%d) FKTable: '%s'(%d)",
szPkTableQualifier ? (char*) szPkTableQualifier : "null", cbPkTableQualifier,
szPkTableOwner ? (char*) szPkTableOwner : "null", cbPkTableOwner,
szPkTableName ? (char*) szPkTableName : "null", cbPkTableName,
szFkTableQualifier ? (char*) szFkTableQualifier : "null", cbFkTableQualifier,
szFkTableOwner ? (char*) szFkTableOwner : "null", cbFkTableOwner,
szFkTableName ? (char*) szFkTableName : "null", cbFkTableName));
if (is_minimum_version(stmt->dbc->mysql.server_version,"3.23",4))
{
STMT FAR *stmt=(STMT FAR*) hstmt;
MEM_ROOT *alloc;
MYSQL_ROW row;
char **data;
char PkQualifier_buff[NAME_LEN+1],PkName_buff[NAME_LEN+1],
*PkTableQualifier,*PkTableName;
char FkQualifier_buff[NAME_LEN+1],FkName_buff[NAME_LEN+1],
*FkTableQualifier,*FkTableName;
uint comment_id;
PkTableQualifier= myodbc_get_valid_buffer((char FAR *)PkQualifier_buff,szPkTableQualifier,
cbPkTableQualifier);
PkTableName= myodbc_get_valid_buffer((char FAR*) PkName_buff,szPkTableName,cbPkTableName);
FkTableQualifier= myodbc_get_valid_buffer((char FAR *)FkQualifier_buff,szFkTableQualifier,
cbFkTableQualifier);
FkTableName= myodbc_get_valid_buffer((char FAR*)FkName_buff,szFkTableName,cbFkTableName);
if (FkTableQualifier && !FkTableQualifier[0])
FkTableQualifier= stmt->dbc->database;
CLEAR_STMT_ERROR(hstmt);
pthread_mutex_lock(&stmt->dbc->lock);
if (!(stmt->result= mysql_table_status(stmt,FkTableQualifier,FkTableName)))
{
DBUG_PRINT("error",("%d:%s", mysql_errno(&stmt->dbc->mysql),
mysql_error(&stmt->dbc->mysql)));
pthread_mutex_unlock(&stmt->dbc->lock);
goto empty_set;
}
pthread_mutex_unlock(&stmt->dbc->lock);
comment_id= stmt->result->field_count-1;
while ((row= mysql_fetch_row(stmt->result)))
{
if ((row[1] && strcmp(row[1],"InnoDB")==0))
{
const char *token,*pktoken,*fk_cols_start,*pk_cols_start;
char *comment_token, ref_token[NAME_LEN+1];
char *pkcomment,*fkcomment;
uint key_seq,pk_length,fk_length;
if (!(comment_token= strchr(row[comment_id],';')))
continue; /* MySQL 4.1 and above, the comment field is '15' */
do
{
/*
Found reference information in comment field from InnoDB type,
and parse the same to get the FK information ..
*/
key_seq= 1;
if (!(token= my_next_token(NULL,&comment_token,NULL,'(')))
break;
fk_cols_start = token;
if (!(token= my_next_token(token,&comment_token,ref_token,')')))
continue;
fk_length= (uint)((token-1)-fk_cols_start);
if (!(token= my_next_token(token+7,&comment_token,ref_token,'/')))
continue;
if (!(token= my_next_token(token,&comment_token,ref_token,'(')) ||
myodbc_casecmp(PkTableName,ref_token,strlen(PkTableName)))
continue;
pk_cols_start = token;
if (!(token= my_next_token(token,&comment_token,ref_token,')')))
continue;
pk_length= (uint)((token-1)-pk_cols_start);
token = fkcomment = (char *)fk_cols_start;
pktoken = pkcomment = (char *)pk_cols_start;
fkcomment[fk_length]= '\0';
pkcomment[pk_length]= '\0';
while (token= my_next_token(token,&fkcomment,ref_token,' '))
{
pktoken= my_next_token(pktoken,&pkcomment,ref_token,' ');
row_count++;
}
row_count++;
} while (comment_token = strchr(comment_token,';'));/* multi table ref */
}
}
stmt->result_array= (char**) my_malloc(sizeof(char*)*SQLFORE_KEYS_FIELDS*
(ulong) row_count,
MYF(MY_FAE | MY_ZEROFILL));
pthread_mutex_lock(&stmt->dbc->lock);
if (!(stmt->result= mysql_table_status(stmt,FkTableQualifier,FkTableName)))
{
DBUG_PRINT("error",("%d:%s", mysql_errno(&stmt->dbc->mysql),
mysql_error(&stmt->dbc->mysql)));
pthread_mutex_unlock(&stmt->dbc->lock);
goto empty_set;
}
pthread_mutex_unlock(&stmt->dbc->lock);
row_count = 0;
/* Convert mysql fields to data that odbc wants */
alloc= &stmt->result->field_alloc;
data= stmt->result_array;
while ((row= mysql_fetch_row(stmt->result)))
{
if ((row[1] && strcmp(row[1],"InnoDB")==0))
{
const char *token,*pktoken,*fk_cols_start,*pk_cols_start;
char *comment_token, ref_token[NAME_LEN+1];
char *pkcomment,*fkcomment;
uint key_seq,pk_length,fk_length;
if (!(comment_token= strchr(row[comment_id],';')))
continue; /* MySQL 4.1 and above, the comment field is '15' */
do
{
/*
Found reference information in comment field from InnoDB type,
and parse the same to get the FK information ..
*/
key_seq= 1;
if (!(token= my_next_token(NULL,&comment_token,NULL,'(')))
break;
fk_cols_start = token;
if (!(token= my_next_token(token,&comment_token,ref_token,')')))
continue;
fk_length= (uint)((token-1)-fk_cols_start);
if (!(token= my_next_token(token+7,&comment_token,ref_token,'/')))
continue;
data[0]= strdup_root(alloc,ref_token); /* PKTABLE_CAT */
if (!(token= my_next_token(token,&comment_token,ref_token,'(')) ||
myodbc_casecmp(PkTableName,ref_token,strlen(PkTableName)))
continue;
data[2]= strdup_root(alloc,ref_token); /* PKTABLE_TABLE */
pk_cols_start = token;
if (!(token= my_next_token(token,&comment_token,ref_token,')')))
continue;
pk_length= (uint)((token-1)-pk_cols_start);
data[1]= ""; /* PKTABLE_SCHEM */
data[4]= strdup_root(alloc,FkTableQualifier); /* FKTABLE_CAT */
data[5]= ""; /* FKTABLE_SCHEM */
data[6]= row[0]; /* FKTABLE_TABLE */
/*
TODO : FIX both UPDATE_RULE and DELETE_RULE after
3.23.52 is released, which supports this feature in
server by updating the 'comment' field as well as
from SHOW CREATE TABLE defination..
right now return only SQL_CASCADE as the DELETE/UPDATE
rule
*/
data[9]= "1"; /*SQL_CASCADE*/ /* UPDATE_RULE */
data[10]= "1"; /*SQL_CASCADE*/ /* DELETE_RULE */
data[11]= "NULL"; /* FK_NAME */
data[12]= "NULL"; /* PK_NAME */
data[13]= "7"; /*SQL_NOT_DEFERRABLE*/ /* DEFERRABILITY */
token = fkcomment = (char *)fk_cols_start;
pktoken = pkcomment = (char *)pk_cols_start;
fkcomment[fk_length]= '\0';
pkcomment[pk_length]= '\0';
while (token= my_next_token(token,&fkcomment,ref_token,' '))
{
/* Multiple columns exists .. parse them to individual rows */
char **prev_data= data;
data[7]= strdup_root(alloc,ref_token); /* FKTABLE_COLUMN */
pktoken= my_next_token(pktoken,&pkcomment,ref_token,' ');
data[3]= strdup_root(alloc,ref_token); /* PKTABLE_COLUMN */
sprintf(ref_token,"%d",key_seq++);
data[8]= strdup_root(alloc,ref_token); /* KEY_SEQ */
data+= SQLFORE_KEYS_FIELDS;
row_count++;
for (fk_length= SQLFORE_KEYS_FIELDS; fk_length--;)
data[fk_length]= prev_data[fk_length];
}
data[7]= strdup_root(alloc,fkcomment); /* FKTABLE_COLUMN */
data[3]= strdup_root(alloc,pkcomment); /* PKTABLE_COLUMN */
sprintf(ref_token,"%d",key_seq);
data[8]= strdup_root(alloc,ref_token); /* KEY_SEQ */
data+= SQLFORE_KEYS_FIELDS;
row_count++;
} while (comment_token = strchr(comment_token,';'));/* multi table ref */
}
}
}
else /* NO FOREIGN KEY support from SERVER */
{
stmt->result=(MYSQL_RES*) my_malloc(sizeof(MYSQL_RES),MYF(MY_ZEROFILL));
stmt->result->eof=1;
}
stmt->result->row_count= row_count;
mysql_link_fields(stmt,SQLFORE_KEYS_fields,SQLFORE_KEYS_FIELDS);
DBUG_PRINT("info ",("total keys count: %ld", row_count));
DBUG_RETURN_STATUS(SQL_SUCCESS);
empty_set:
DBUG_PRINT("info ",("Can't match anything; Returning empty set"));
stmt->result= (MYSQL_RES*) my_malloc(sizeof(MYSQL_RES),MYF(MY_ZEROFILL));
stmt->result->row_count= 0;
stmt->result_array= (MYSQL_ROW) my_memdup((gptr) SQLFORE_KEYS_values,
sizeof(SQLFORE_KEYS_values),
MYF(0));
mysql_link_fields(stmt,SQLFORE_KEYS_fields,SQLFORE_KEYS_FIELDS);
DBUG_RETURN_STATUS(SQL_SUCCESS);
}