/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/ODBC is licensed under the terms of the GPLv2 , like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** @file catalog.c @brief Catalog functions. */ #include "driver.h" #include "catalog.h" /* @type : internal @purpose : checks if server supports I_S @remark : All i_s_* functions suppose that parameters specifying other parameters lenthes can't SQL_NTS. caller should take care of that. */ my_bool server_has_i_s(DBC FAR *dbc) { /* According to the server ChangeLog INFORMATION_SCHEMA was introduced in the 5.0.2 */ return is_minimum_version(dbc->mysql.server_version, "5.0.2", 5); } ulong no_information_schema(STMT FAR *stmt) { return stmt->dbc->flag & FLAG_NO_INFORMATION_SCHEMA; } /* @type : internal @purpose : returns the next token */ const char *my_next_token(const char *prev_token, char **token, char *data, const char chr) { const char *cur_token; if ( (cur_token= strchr(*token, chr)) ) { if ( prev_token ) { uint len= (uint)(cur_token-prev_token); strncpy(data,prev_token, len); data[len]= 0; } *token= (char *)cur_token+1; prev_token= cur_token; return cur_token+1; } return 0; } /** Create a fake result set in the current statement @param[in] stmt Handle to statement @param[in] rowval Row array @param[in] rowsize sizeof(row array) @param[in] rowcnt Number of rows @param[in] fields Field array @param[in] fldcnt Number of fields @return SQL_SUCCESS or SQL_ERROR (and diag is set) */ SQLRETURN create_fake_resultset(STMT *stmt, MYSQL_ROW rowval, size_t rowsize, my_ulonglong rowcnt, MYSQL_FIELD *fields, uint fldcnt) { stmt->result= (MYSQL_RES*) my_malloc(sizeof(MYSQL_RES), MYF(MY_ZEROFILL)); stmt->result_array= (MYSQL_ROW)my_memdup((char *)rowval, rowsize, MYF(0)); if (!(stmt->result && stmt->result_array)) { x_free(stmt->result); x_free(stmt->result_array); set_mem_error(&stmt->dbc->mysql); return handle_connection_error(stmt); } stmt->fake_result= 1; set_row_count(stmt, rowcnt); mysql_link_fields(stmt, fields, fldcnt); return SQL_SUCCESS; } /** Create an empty fake result set in the current statement @param[in] stmt Handle to statement @param[in] rowval Row array @param[in] rowsize sizeof(row array) @param[in] fields Field array @param[in] fldcnt Number of fields @return SQL_SUCCESS or SQL_ERROR (and diag is set) */ SQLRETURN create_empty_fake_resultset(STMT *stmt, MYSQL_ROW rowval, size_t rowsize, MYSQL_FIELD *fields, uint fldcnt) { return create_fake_resultset(stmt, rowval, rowsize, 0 /* rowcnt */, fields, fldcnt); } /** Get the table status for a table or tables using Information_Schema DB. Lengths may not be SQL_NTS. @param[in] stmt Handle to statement @param[in] catalog Catalog (database) of table, @c NULL for current @param[in] catalog_length Length of catalog name @param[in] table Name of table @param[in] table_length Length of table name @param[in] wildcard Whether the table name is a wildcard @return Result of SHOW TABLE STATUS, or NULL if there is an error or empty result (check mysql_errno(&stmt->dbc->mysql) != 0) */ static MYSQL_RES *mysql_table_status_i_s(STMT *stmt, SQLCHAR *catalog, SQLSMALLINT catalog_length, SQLCHAR *table, SQLSMALLINT table_length, my_bool wildcard, my_bool show_tables, my_bool show_views) { MYSQL *mysql= &stmt->dbc->mysql; /** @todo determine real size for buffer */ char buff[255], *to; my_bool clause_added= FALSE; to= strmov(buff, "SELECT TABLE_NAME, TABLE_COMMENT, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE "); if (catalog && *catalog) { to= strmov(to, "TABLE_SCHEMA LIKE '"); to+= myodbc_escape_string(mysql, to, (ulong)(sizeof(buff) - (to - buff)), (char *)catalog, catalog_length, 1); to= strmov(to, "' "); clause_added= TRUE; } else { to= strmov(to, "TABLE_SCHEMA = DATABASE() "); } if (show_tables) { to= strmov(to, "AND "); if (show_views) to= strmov(to, "( "); to= strmov(to, "TABLE_TYPE='BASE TABLE' "); } if (show_views) { if (show_tables) to= strmov(to, "OR "); else to= strmov(to, "AND "); to= strmov(to, "TABLE_TYPE='VIEW' "); if (show_tables) to= strmov(to, ") "); } /* As a pattern-value argument, an empty string needs to be treated literally. (It's not the same as NULL, which is the same as '%'.) But it will never match anything, so bail out now. */ if (table && wildcard && !*table) return NULL; if (table && *table) { to= strmov(to, "AND TABLE_NAME LIKE '"); if (wildcard) to+= mysql_real_escape_string(mysql, to, (char *)table, table_length); else to+= myodbc_escape_string(mysql, to, (ulong)(sizeof(buff) - (to - buff)), (char *)table, table_length, 0); to= strmov(to, "'"); } assert(to - buff < sizeof(buff)); MYLOG_QUERY(stmt, buff); if (mysql_real_query(mysql, buff, (unsigned long)(to - buff))) return NULL; return mysql_store_result(mysql); } /** Get the table status for a table or tables. Lengths may not be SQL_NTS. @param[in] stmt Handle to statement @param[in] catalog Catalog (database) of table, @c NULL for current @param[in] catalog_length Length of catalog name @param[in] table Name of table @param[in] table_length Length of table name @param[in] wildcard Whether the table name is a wildcard @return Result of SHOW TABLE STATUS, or NULL if there is an error or empty result (check mysql_errno(&stmt->dbc->mysql) != 0) */ MYSQL_RES *mysql_table_status(STMT *stmt, SQLCHAR *catalog, SQLSMALLINT catalog_length, SQLCHAR *table, SQLSMALLINT table_length, my_bool wildcard, my_bool show_tables, my_bool show_views) { if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) return mysql_table_status_i_s(stmt, catalog, catalog_length, table, table_length, wildcard, show_tables, show_views); else return mysql_table_status_show(stmt, catalog, catalog_length, table, table_length, wildcard); } /* @type : internal @purpose : Adding name condition for arguments what depending on SQL_ATTR_METADATA_ID are either ordinary argument(oa) or identifier string(id) NULL _default parameter means that parameter is mandatory and error is generated @returns : 1 if required parameter is NULL, 0 otherwise */ int add_name_condition_oa_id(HSTMT hstmt, char ** pos, SQLCHAR * name, SQLSMALLINT name_len, char * _default) { SQLUINTEGER metadata_id; /* this shouldn't be very expensive, so no need to complicate things with additional parameter etc */ SQLGetStmtAttr(hstmt, SQL_ATTR_METADATA_ID, (SQLPOINTER)&metadata_id, 0, NULL); /* we can't rely here that column was checked and is not null */ if (name) { STMT FAR *stmt= (STMT FAR*) hstmt; if (metadata_id) { *pos= strmov(*pos, "="); /* Need also code to remove trailing blanks */ } else *pos= strmov(*pos, "= BINARY "); *pos= strmov(*pos, "'"); *pos+= mysql_real_escape_string(&stmt->dbc->mysql, *pos, (char *)name, name_len); *pos= strmov(*pos, "' "); } else { /* According to http://msdn.microsoft.com/en-us/library/ms714579%28VS.85%29.aspx identifier argument cannot be NULL with one exception not actual for mysql) */ if (!metadata_id && _default) *pos= strmov(*pos, _default); else return 1; } return 0; } /* @type : internal @purpose : Adding name condition for arguments what depending on SQL_ATTR_METADATA_ID are either pattern value(oa) or identifier string(id) @returns : 1 if required parameter is NULL, 0 otherwise */ int add_name_condition_pv_id(HSTMT hstmt, char ** pos, SQLCHAR * name, SQLSMALLINT name_len, char * _default) { SQLUINTEGER metadata_id; /* this shouldn't be very expensive, so no need to complicate things with additional parameter etc */ SQLGetStmtAttr(hstmt, SQL_ATTR_METADATA_ID, (SQLPOINTER)&metadata_id, 0, NULL); /* we can't rely here that column was checked and is not null */ if (name) { STMT FAR *stmt= (STMT FAR*) hstmt; if (metadata_id) { *pos= strmov(*pos, "="); /* Need also code to remove trailing blanks */ } else *pos= strmov(*pos, " LIKE BINARY "); *pos= strmov(*pos, "'"); *pos+= mysql_real_escape_string(&stmt->dbc->mysql, *pos, (char *)name, name_len); *pos= strmov(*pos, "' "); } else { /* According to http://msdn.microsoft.com/en-us/library/ms714579%28VS.85%29.aspx identifier argument cannot be NULL with one exception not actual for mysql) */ if (!metadata_id && _default) *pos= strmov(*pos, _default); else return 1; } return 0; } /* **************************************************************************** SQLTables **************************************************************************** */ SQLRETURN i_s_tables(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLCHAR *type, SQLSMALLINT type_len) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_tables(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, type, type_len); } SQLRETURN SQL_API SQLTables(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema, SQLSMALLINT schema_len, SQLCHAR *table, SQLSMALLINT table_len, SQLCHAR *type, SQLSMALLINT type_len) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt, MYSQL_RESET); if (catalog_len == SQL_NTS) catalog_len= catalog ? (SQLSMALLINT)strlen((char *)catalog) : 0; if (schema_len == SQL_NTS) schema_len= schema ? (SQLSMALLINT)strlen((char *)schema) : 0; if (table_len == SQL_NTS) table_len= table ? (SQLSMALLINT)strlen((char *)table) : 0; if (type_len == SQL_NTS) type_len= type ? (SQLSMALLINT)strlen((char *)type) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_tables(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, type, type_len); } else { return mysql_tables(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, type, type_len); } } /* **************************************************************************** SQLColumns **************************************************************************** */ /** Get information about the columns in one or more tables. @param[in] hstmt Handle of statement @param[in] szCatalog Name of catalog (database) @param[in] cbCatalog Length of catalog @param[in] szSchema Name of schema (unused) @param[in] cbSchema Length of schema name @param[in] szTable Pattern of table names to match @param[in] cbTable Length of table pattern @param[in] szColumn Pattern of column names to match @param[in] cbColumn Length of column pattern */ SQLRETURN i_s_columns(STMT *stmt, SQLCHAR *szCatalog, SQLSMALLINT cbCatalog, SQLCHAR *szSchema __attribute__((unused)), SQLSMALLINT cbSchema __attribute__((unused)), SQLCHAR *szTable, SQLSMALLINT cbTable, SQLCHAR *szColumn, SQLSMALLINT cbColumn) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_columns(stmt, szCatalog, cbCatalog,szSchema, cbSchema, szTable, cbTable, szColumn, cbColumn); } /** Get information about the columns in one or more tables. @param[in] hstmt Handle of statement @param[in] szCatalog Name of catalog (database) @param[in] cbCatalog Length of catalog @param[in] szSchema Name of schema (unused) @param[in] cbSchema Length of schema name @param[in] szTable Pattern of table names to match @param[in] cbTable Length of table pattern @param[in] szColumn Pattern of column names to match @param[in] cbColumn Length of column pattern */ SQLRETURN SQL_API SQLColumns(SQLHSTMT hstmt, SQLCHAR *szCatalog, SQLSMALLINT cbCatalog, SQLCHAR *szSchema __attribute__((unused)), SQLSMALLINT cbSchema __attribute__((unused)), SQLCHAR *szTable, SQLSMALLINT cbTable, SQLCHAR *szColumn, SQLSMALLINT cbColumn) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt, MYSQL_RESET); if (cbCatalog == SQL_NTS) cbCatalog= szCatalog != NULL ? (SQLSMALLINT)strlen((char *)szCatalog) : 0; if (cbColumn == SQL_NTS) cbColumn= szColumn != NULL ? (SQLSMALLINT)strlen((char *)szColumn) : 0; if (cbTable == SQL_NTS) cbTable= szTable != NULL ? (SQLSMALLINT)strlen((char *)szTable) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_columns(stmt, szCatalog, cbCatalog,szSchema, cbSchema, szTable, cbTable, szColumn, cbColumn); } else { return mysql_columns(stmt, szCatalog, cbCatalog,szSchema, cbSchema, szTable, cbTable, szColumn, cbColumn); } } /* **************************************************************************** SQLStatistics **************************************************************************** */ /* @purpose : retrieves a list of statistics about a single table and the indexes associated with the table. The driver returns the information as a result set. */ SQLRETURN i_s_statistics(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len, SQLUSMALLINT fUnique, SQLUSMALLINT fAccuracy __attribute__((unused))) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_statistics(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, fUnique, fAccuracy); } /* @type : ODBC 1.0 API @purpose : retrieves a list of statistics about a single table and the indexes associated with the table. The driver returns the information as a result set. */ SQLRETURN SQL_API SQLStatistics(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len, SQLUSMALLINT fUnique, SQLUSMALLINT fAccuracy __attribute__((unused))) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (catalog_len == SQL_NTS) catalog_len= catalog ? (SQLSMALLINT)strlen((char *)catalog) : 0; if (table_len == SQL_NTS) table_len= table ? (SQLSMALLINT)strlen((char *)table) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_statistics(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, fUnique, fAccuracy); } else { return mysql_statistics(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, fUnique, fAccuracy); } } /* **************************************************************************** SQLTablePrivileges **************************************************************************** */ /* @type : internal @purpose : fetches data from I_S table_privileges. returns SQLRETURN of the operation */ SQLRETURN i_s_list_table_priv(SQLHSTMT hstmt, SQLCHAR * catalog, SQLSMALLINT catalog_len, SQLCHAR * schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR * table, SQLSMALLINT table_len) { STMT FAR *stmt=(STMT FAR*) hstmt; MYSQL *mysql= &stmt->dbc->mysql; char buff[255+4*NAME_LEN+1], *pos; SQLRETURN rc; /* Db,User,Table_name,"NULL" as Grantor,Table_priv*/ pos= strmov(buff, "SELECT TABLE_SCHEMA as TABLE_CAT, TABLE_CATALOG as TABLE_SCHEM," "TABLE_NAME, NULL as GRANTOR, GRANTEE," "PRIVILEGE_TYPE as PRIVILEGE, IS_GRANTABLE " "FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES " "WHERE TABLE_NAME"); add_name_condition_pv_id(hstmt, &pos, table, table_len, " LIKE '%'" ); pos= strmov(pos, " AND TABLE_SCHEMA"); add_name_condition_oa_id(hstmt, &pos, catalog, catalog_len, "=DATABASE()"); /* TABLE_CAT is always NULL in mysql I_S */ pos= strmov(pos, " ORDER BY /*TABLE_CAT,*/ TABLE_SCHEM, TABLE_NAME, PRIVILEGE, GRANTEE"); assert(pos - buff < sizeof(buff)); if( !SQL_SUCCEEDED(rc= my_SQLPrepare(hstmt, (SQLCHAR *)buff, (SQLINTEGER)(pos - buff)))) return rc; return my_SQLExecute(stmt); } /* @type : ODBC 1.0 API @purpose : returns a list of tables and the privileges associated with each table. The driver returns the information as a result set on the specified statement. */ SQLRETURN SQL_API SQLTablePrivileges(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (catalog_len == SQL_NTS) catalog_len= catalog ? (SQLSMALLINT)strlen((char *)catalog) : 0; if (table_len == SQL_NTS) table_len= table ? (SQLSMALLINT)strlen((char *)table) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { /* Since mysql is also the name of the system db, using here i_s prefix to distinct functions */ return i_s_list_table_priv(hstmt, catalog, catalog_len, schema, schema_len, table, table_len); } else { return mysql_list_table_priv(hstmt, catalog, catalog_len, schema, schema_len, table, table_len); } } /* @type : internal @purpose : returns a column privileges result, NULL on error */ static SQLRETURN i_s_list_column_priv(HSTMT * hstmt, SQLCHAR * catalog, SQLSMALLINT catalog_len, SQLCHAR * schema, SQLSMALLINT schema_len, SQLCHAR * table, SQLSMALLINT table_len, SQLCHAR * column, SQLSMALLINT column_len) { STMT FAR *stmt=(STMT FAR*) hstmt; MYSQL *mysql= &stmt->dbc->mysql; /* 3 names theorethically can have all their characters escaped - thus 6*NAME_LEN */ char buff[351+6*NAME_LEN+1], *pos; SQLRETURN rc; /* Db,User,Table_name,"NULL" as Grantor,Table_priv*/ pos= strmov(buff, "SELECT TABLE_SCHEMA as TABLE_CAT, TABLE_CATALOG as TABLE_SCHEM," "TABLE_NAME, COLUMN_NAME, NULL as GRANTOR, GRANTEE," "PRIVILEGE_TYPE as PRIVILEGE, IS_GRANTABLE " "FROM INFORMATION_SCHEMA.COLUMN_PRIVILEGES " "WHERE TABLE_NAME"); if(add_name_condition_oa_id(hstmt, &pos, table, table_len, NULL)) return set_stmt_error(stmt, "HY009", "Invalid use of NULL pointer(table is required parameter)", 0); pos= strmov(pos, " AND TABLE_SCHEMA"); add_name_condition_oa_id(hstmt, &pos, catalog, catalog_len, "=DATABASE()"); pos= strmov(pos, " AND COLUMN_NAME"); add_name_condition_pv_id(hstmt, &pos, column, column_len, " LIKE '%'"); /* TABLE_CAT is always NULL in mysql I_S */ pos= strmov(pos, " ORDER BY /*TABLE_CAT,*/ TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, PRIVILEGE"); assert(pos - buff < sizeof(buff)); if( !SQL_SUCCEEDED(rc= my_SQLPrepare(hstmt, (SQLCHAR *)buff, SQL_NTS))) return rc; return my_SQLExecute(stmt); } SQLRETURN SQL_API SQLColumnPrivileges(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len, SQLCHAR *column, SQLSMALLINT column_len) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (catalog_len == SQL_NTS) catalog_len= catalog ? (SQLSMALLINT)strlen((char *)catalog) : 0; if (table_len == SQL_NTS) table_len= table ? (SQLSMALLINT)strlen((char *)table) : 0; if (column_len == SQL_NTS) column_len= column ? (SQLSMALLINT)strlen((char *)column) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { /* Since mysql is also the name of the system db, using here i_s prefix to distinct functions */ return i_s_list_column_priv(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, column, column_len); } else { return mysql_list_column_priv(hstmt, catalog, catalog_len, schema, schema_len, table, table_len, column, column_len); } } /* **************************************************************************** SQLSpecialColumns **************************************************************************** */ SQLRETURN i_s_special_columns(SQLHSTMT hstmt, SQLUSMALLINT fColType, SQLCHAR *szTableQualifier, SQLSMALLINT cbTableQualifier, SQLCHAR *szTableOwner __attribute__((unused)), SQLSMALLINT cbTableOwner __attribute__((unused)), SQLCHAR *szTableName, SQLSMALLINT cbTableName, SQLUSMALLINT fScope __attribute__((unused)), SQLUSMALLINT fNullable __attribute__((unused))) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_special_columns(hstmt, fColType, szTableQualifier, cbTableQualifier, szTableOwner, cbTableOwner, szTableName, cbTableName, fScope, fNullable); } /* @type : ODBC 1.0 API @purpose : retrieves the following information about columns within a specified table: - The optimal set of columns that uniquely identifies a row in the table. - Columns that are automatically updated when any value in the row is updated by a transaction */ SQLRETURN SQL_API SQLSpecialColumns(SQLHSTMT hstmt, SQLUSMALLINT fColType, SQLCHAR *szTableQualifier, SQLSMALLINT cbTableQualifier, SQLCHAR *szTableOwner __attribute__((unused)), SQLSMALLINT cbTableOwner __attribute__((unused)), SQLCHAR *szTableName, SQLSMALLINT cbTableName, SQLUSMALLINT fScope __attribute__((unused)), SQLUSMALLINT fNullable __attribute__((unused))) { STMT *stmt=(STMT *) hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (cbTableQualifier == SQL_NTS) cbTableQualifier= szTableQualifier ? (SQLSMALLINT)strlen((char *)szTableQualifier) : 0; if (cbTableName == SQL_NTS) cbTableName= szTableName ? (SQLSMALLINT)strlen((char *)szTableName) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_special_columns(hstmt, fColType, szTableQualifier, cbTableQualifier, szTableOwner, cbTableOwner, szTableName, cbTableName, fScope, fNullable); } else { return mysql_special_columns(hstmt, fColType, szTableQualifier, cbTableQualifier, szTableOwner, cbTableOwner, szTableName, cbTableName, fScope, fNullable); } } /* **************************************************************************** SQLPrimaryKeys **************************************************************************** */ SQLRETURN i_s_primary_keys(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_primary_keys(hstmt, catalog, catalog_len, schema, schema_len, table, table_len); } /* @type : ODBC 1.0 API @purpose : returns the column names that make up the primary key for a table. The driver returns the information as a result set. This function does not support returning primary keys from multiple tables in a single call */ SQLRETURN SQL_API SQLPrimaryKeys(SQLHSTMT hstmt, SQLCHAR *catalog, SQLSMALLINT catalog_len, SQLCHAR *schema __attribute__((unused)), SQLSMALLINT schema_len __attribute__((unused)), SQLCHAR *table, SQLSMALLINT table_len) { STMT FAR *stmt= (STMT FAR*) hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (catalog_len == SQL_NTS) catalog_len= catalog ? (SQLSMALLINT)strlen((char *)catalog) : 0; if (table_len == SQL_NTS) table_len= table ? (SQLSMALLINT)strlen((char *)table) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_primary_keys(hstmt, catalog, catalog_len, schema, schema_len, table, table_len); } else { return mysql_primary_keys(hstmt, catalog, catalog_len, schema, schema_len, table, table_len); } } /* **************************************************************************** SQLForeignKeys **************************************************************************** */ SQLRETURN i_s_foreign_keys(SQLHSTMT hstmt, SQLCHAR *szPkCatalogName __attribute__((unused)), SQLSMALLINT cbPkCatalogName __attribute__((unused)), SQLCHAR *szPkSchemaName __attribute__((unused)), SQLSMALLINT cbPkSchemaName __attribute__((unused)), SQLCHAR *szPkTableName, SQLSMALLINT cbPkTableName, SQLCHAR *szFkCatalogName, SQLSMALLINT cbFkCatalogName, SQLCHAR *szFkSchemaName __attribute__((unused)), SQLSMALLINT cbFkSchemaName __attribute__((unused)), SQLCHAR *szFkTableName, SQLSMALLINT cbFkTableName) { STMT FAR *stmt=(STMT FAR*) hstmt; MYSQL *mysql= &stmt->dbc->mysql; char query[2048], *buff; /* This should be big enough. */ char *update_rule, *delete_rule, *ref_constraints_join; SQLRETURN rc; /* With 5.1, we can use REFERENTIAL_CONSTRAINTS to get even more info. */ if (is_minimum_version(stmt->dbc->mysql.server_version, "5.1", 3)) { update_rule= "CASE" " WHEN R.UPDATE_RULE = 'CASCADE' THEN 0" " WHEN R.UPDATE_RULE = 'SET NULL' THEN 2" " WHEN R.UPDATE_RULE = 'SET DEFAULT' THEN 4" " WHEN R.UPDATE_RULE = 'SET RESTRICT' THEN 1" " WHEN R.UPDATE_RULE = 'SET NO ACTION' THEN 3" " ELSE 3" " END"; delete_rule= "CASE" " WHEN R.DELETE_RULE = 'CASCADE' THEN 0" " WHEN R.DELETE_RULE = 'SET NULL' THEN 2" " WHEN R.DELETE_RULE = 'SET DEFAULT' THEN 4" " WHEN R.DELETE_RULE = 'SET RESTRICT' THEN 1" " WHEN R.DELETE_RULE = 'SET NO ACTION' THEN 3" " ELSE 3" " END"; ref_constraints_join= " JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS R" " ON (R.CONSTRAINT_NAME = A.CONSTRAINT_NAME" " AND R.TABLE_NAME = A.TABLE_NAME" " AND R.CONSTRAINT_SCHEMA = A.TABLE_SCHEMA)"; } else { /* Just return '1' to be compatible with pre-I_S version. */ update_rule= delete_rule= "1"; ref_constraints_join= ""; } /* This is a big, ugly query. But it works! */ buff= strxmov(query, "SELECT A.REFERENCED_TABLE_SCHEMA AS PKTABLE_CAT," "NULL AS PKTABLE_SCHEM," "A.REFERENCED_TABLE_NAME AS PKTABLE_NAME," "A.REFERENCED_COLUMN_NAME AS PKCOLUMN_NAME," "A.TABLE_SCHEMA AS FKTABLE_CAT, NULL AS FKTABLE_SCHEM," "A.TABLE_NAME AS FKTABLE_NAME," "A.COLUMN_NAME AS FKCOLUMN_NAME," "A.ORDINAL_POSITION AS KEY_SEQ,", update_rule, " AS UPDATE_RULE,", delete_rule, " AS DELETE_RULE," "A.CONSTRAINT_NAME AS FK_NAME," "'PRIMARY' AS PK_NAME," "7 AS DEFERRABILITY" " FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE A" " JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE D" " ON (D.TABLE_SCHEMA=A.REFERENCED_TABLE_SCHEMA AND D.TABLE_NAME=A.REFERENCED_TABLE_NAME" " AND D.COLUMN_NAME=A.REFERENCED_COLUMN_NAME)", ref_constraints_join, " WHERE D.CONSTRAINT_NAME='PRIMARY' ", NullS); if (szPkTableName && szPkTableName[0]) { buff= strmov(buff, "AND A.REFERENCED_TABLE_SCHEMA = "); if (szPkCatalogName && szPkCatalogName[0]) { buff= strmov(buff, "'"); buff+= mysql_real_escape_string(mysql, buff, (char *)szPkCatalogName, cbPkCatalogName); buff= strmov(buff, "' "); } else { buff= strmov(buff, "DATABASE() "); } buff= strmov(buff, "AND A.REFERENCED_TABLE_NAME = '"); buff+= mysql_real_escape_string(mysql, buff, (char *)szPkTableName, cbPkTableName); buff= strmov(buff, "' "); strmov(buff, "ORDER BY PKTABLE_CAT, PKTABLE_NAME, " "KEY_SEQ, FKTABLE_NAME"); } if (szFkTableName && szFkTableName[0]) { buff= strmov(buff, "AND A.TABLE_SCHEMA = "); if (szFkCatalogName && szFkCatalogName[0]) { buff= strmov(buff, "'"); buff+= mysql_real_escape_string(mysql, buff, (char *)szFkCatalogName, cbFkCatalogName); buff= strmov(buff, "' "); } else { buff= strmov(buff, "DATABASE() "); } buff= strmov(buff, "AND A.TABLE_NAME = '"); buff+= mysql_real_escape_string(mysql, buff, (char *)szFkTableName, cbFkTableName); buff= strmov(buff, "' "); buff= strmov(buff, "ORDER BY FKTABLE_CAT, FKTABLE_NAME, " "KEY_SEQ, PKTABLE_NAME"); } assert(buff - query < sizeof(query)); rc= my_SQLPrepare(hstmt, (SQLCHAR *)query, (SQLINTEGER)(buff - query)); if (!SQL_SUCCEEDED(rc)) return rc; return my_SQLExecute(hstmt); } /** Retrieve either a list of foreign keys in a specified table, or the list of foreign keys in other tables that refer to the primary key in the specified table. (We currently only support the former, not the latter.) @param[in] hstmt Handle of statement @param[in] szPkCatalogName Catalog (database) of table with primary key that we want to see foreign keys for @param[in] cbPkCatalogName Length of @a szPkCatalogName @param[in] szPkSchemaName Schema of table with primary key that we want to see foreign keys for (unused) @param[in] cbPkSchemaName Length of @a szPkSchemaName @param[in] szPkTableName Table with primary key that we want to see foreign keys for @param[in] cbPkTableName Length of @a szPkTableName @param[in] szFkCatalogName Catalog (database) of table with foreign keys we are interested in @param[in] cbFkCatalogName Length of @a szFkCatalogName @param[in] szFkSchemaName Schema of table with foreign keys we are interested in @param[in] cbFkSchemaName Length of szFkSchemaName @param[in] szFkTableName Table with foreign keys we are interested in @param[in] cbFkTableName Length of @a szFkTableName @return SQL_SUCCESS @since ODBC 1.0 */ SQLRETURN SQL_API SQLForeignKeys(SQLHSTMT hstmt, SQLCHAR *szPkCatalogName __attribute__((unused)), SQLSMALLINT cbPkCatalogName __attribute__((unused)), SQLCHAR *szPkSchemaName __attribute__((unused)), SQLSMALLINT cbPkSchemaName __attribute__((unused)), SQLCHAR *szPkTableName, SQLSMALLINT cbPkTableName, SQLCHAR *szFkCatalogName, SQLSMALLINT cbFkCatalogName, SQLCHAR *szFkSchemaName __attribute__((unused)), SQLSMALLINT cbFkSchemaName __attribute__((unused)), SQLCHAR *szFkTableName, SQLSMALLINT cbFkTableName) { STMT FAR *stmt=(STMT FAR*) hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (cbPkTableName == SQL_NTS) cbPkTableName= szPkTableName != NULL ? (SQLSMALLINT)strlen((char *)szPkTableName) : 0; if (cbPkCatalogName == SQL_NTS) cbPkCatalogName= szPkCatalogName != NULL ? (SQLSMALLINT)strlen((char *)szPkCatalogName) : 0; if (cbFkCatalogName == SQL_NTS) cbFkCatalogName= szFkCatalogName != NULL ? (SQLSMALLINT)strlen((char *)szFkCatalogName) : 0; if (cbFkTableName == SQL_NTS) cbFkTableName= szFkTableName != NULL ? (SQLSMALLINT)strlen((char *)szFkTableName) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_foreign_keys(hstmt, szPkCatalogName, cbPkCatalogName, szPkSchemaName, cbPkSchemaName, szPkTableName, cbPkTableName, szFkCatalogName, cbFkCatalogName, szFkSchemaName, cbFkSchemaName, szFkTableName, cbFkTableName); } /* For 3.23 and later, use comment in SHOW TABLE STATUS (yuck). */ else /* We wouldn't get here if we had server version under 3.23 */ { return mysql_foreign_keys(hstmt, szPkCatalogName, cbPkCatalogName, szPkSchemaName, cbPkSchemaName, szPkTableName, cbPkTableName, szFkCatalogName, cbFkCatalogName, szFkSchemaName, cbFkSchemaName, szFkTableName, cbFkTableName); } } /* **************************************************************************** SQLProcedures and SQLProcedureColumns **************************************************************************** */ /** Get the list of procedures stored in a catalog (database). This is done by generating the appropriate query against INFORMATION_SCHEMA. If no database is specified, the current database is used. @param[in] hstmt Handle of statement @param[in] szCatalogName Name of catalog (database) @param[in] cbCatalogName Length of catalog @param[in] szSchemaName Pattern of schema (unused) @param[in] cbSchemaName Length of schema name @param[in] szProcName Pattern of procedure names to fetch @param[in] cbProcName Length of procedure name */ SQLRETURN SQL_API SQLProcedures(SQLHSTMT hstmt, SQLCHAR *szCatalogName, SQLSMALLINT cbCatalogName, SQLCHAR *szSchemaName __attribute__((unused)), SQLSMALLINT cbSchemaName __attribute__((unused)), SQLCHAR *szProcName, SQLSMALLINT cbProcName) { SQLRETURN rc; STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); /* If earlier than 5.0, the server doesn't even support stored procs. */ if (!server_has_i_s(stmt->dbc)) { /* We use the server to generate a fake result with no rows, but reasonable column information. */ if ((rc= my_SQLPrepare(hstmt, (SQLCHAR *)"SELECT " "'' AS PROCEDURE_CAT," "'' AS PROCEDURE_SCHEM," "'' AS PROCEDURE_NAME," "NULL AS NUM_INPUT_PARAMS," "NULL AS NUM_OUTPUT_PARAMS," "NULL AS NUM_RESULT_SETS," "'' AS REMARKS," "0 AS PROCEDURE_TYPE " "FROM DUAL WHERE 1=0", SQL_NTS))) return rc; return my_SQLExecute(hstmt); } /* If a catalog (database) was specified, we use that, otherwise we look up procedures from the current database. (This is not standard behavior, but seems useful.) */ if (szCatalogName && szProcName) rc= my_SQLPrepare(hstmt, (SQLCHAR *) "SELECT ROUTINE_SCHEMA AS PROCEDURE_CAT," "NULL AS PROCEDURE_SCHEM," "ROUTINE_NAME AS PROCEDURE_NAME," "NULL AS NUM_INPUT_PARAMS," "NULL AS NUM_OUTPUT_PARAMS," "NULL AS NUM_RESULT_SETS," "ROUTINE_COMMENT AS REMARKS," "IF(ROUTINE_TYPE = 'FUNCTION', 2," "IF(ROUTINE_TYPE= 'PROCEDURE', 1, 0)) AS PROCEDURE_TYPE" " FROM INFORMATION_SCHEMA.ROUTINES" " WHERE ROUTINE_NAME LIKE ? AND ROUTINE_SCHEMA = ?", SQL_NTS); else if (szProcName) rc= my_SQLPrepare(hstmt, (SQLCHAR *) "SELECT ROUTINE_SCHEMA AS PROCEDURE_CAT," "NULL AS PROCEDURE_SCHEM," "ROUTINE_NAME AS PROCEDURE_NAME," "NULL AS NUM_INPUT_PARAMS," "NULL AS NUM_OUTPUT_PARAMS," "NULL AS NUM_RESULT_SETS," "ROUTINE_COMMENT AS REMARKS," "IF(ROUTINE_TYPE = 'FUNCTION', 2," "IF(ROUTINE_TYPE= 'PROCEDURE', 1, 0)) AS PROCEDURE_TYPE" " FROM INFORMATION_SCHEMA.ROUTINES" " WHERE ROUTINE_NAME LIKE ?" " AND ROUTINE_SCHEMA = DATABASE()", SQL_NTS); else rc= my_SQLPrepare(hstmt, (SQLCHAR *) "SELECT ROUTINE_SCHEMA AS PROCEDURE_CAT," "NULL AS PROCEDURE_SCHEM," "ROUTINE_NAME AS PROCEDURE_NAME," "NULL AS NUM_INPUT_PARAMS," "NULL AS NUM_OUTPUT_PARAMS," "NULL AS NUM_RESULT_SETS," "ROUTINE_COMMENT AS REMARKS," "IF(ROUTINE_TYPE = 'FUNCTION', 2," "IF(ROUTINE_TYPE= 'PROCEDURE', 1, 0)) AS PROCEDURE_TYPE" " FROM INFORMATION_SCHEMA.ROUTINES" " WHERE ROUTINE_SCHEMA = DATABASE()", SQL_NTS); if (!SQL_SUCCEEDED(rc)) return rc; if (szProcName) { if (cbProcName == SQL_NTS) cbProcName= (SQLSMALLINT)strlen((const char *)szProcName); rc= my_SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_C_CHAR, 0, 0, szProcName, cbProcName, NULL); if (!SQL_SUCCEEDED(rc)) return rc; } if (szCatalogName) { if (cbCatalogName == SQL_NTS) cbCatalogName= (SQLSMALLINT)strlen((const char *)szCatalogName); rc= my_SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_C_CHAR, 0, 0, szCatalogName, cbCatalogName, NULL); if (!SQL_SUCCEEDED(rc)) return rc; } return my_SQLExecute(hstmt); } /* **************************************************************************** SQLProcedure Columns **************************************************************************** */ /* @purpose : returns the list of input and output parameters, as well as the columns that make up the result set for the specified procedures. The driver returns the information as a result set on the specified statement */ SQLRETURN i_s_procedure_columns(SQLHSTMT hstmt, SQLCHAR *szCatalogName, SQLSMALLINT cbCatalogName, SQLCHAR *szSchemaName __attribute__((unused)), SQLSMALLINT cbSchemaName __attribute__((unused)), SQLCHAR *szProcName, SQLSMALLINT cbProcName, SQLCHAR *szColumnName, SQLSMALLINT cbColumnName) { /* The function is just a stub. We call non-I_S version of the function before implementing the I_S one */ return mysql_procedure_columns(hstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szProcName, cbProcName, szColumnName, cbColumnName); } /* @type : ODBC 1.0 API @purpose : returns the list of input and output parameters, as well as the columns that make up the result set for the specified procedures. The driver returns the information as a result set on the specified statement */ SQLRETURN SQL_API SQLProcedureColumns(SQLHSTMT hstmt, SQLCHAR *szCatalogName, SQLSMALLINT cbCatalogName, SQLCHAR *szSchemaName __attribute__((unused)), SQLSMALLINT cbSchemaName __attribute__((unused)), SQLCHAR *szProcName, SQLSMALLINT cbProcName, SQLCHAR *szColumnName, SQLSMALLINT cbColumnName) { STMT *stmt= (STMT *)hstmt; CLEAR_STMT_ERROR(hstmt); my_SQLFreeStmt(hstmt,MYSQL_RESET); if (cbCatalogName == SQL_NTS) cbCatalogName= szCatalogName ? (SQLSMALLINT)strlen((char *)szCatalogName) : 0; if (cbProcName == SQL_NTS) cbProcName= szProcName ? (SQLSMALLINT)strlen((char *)szProcName) : 0; if (cbColumnName == SQL_NTS) cbColumnName= szColumnName ? (SQLSMALLINT)strlen((char *)szColumnName) : 0; if (server_has_i_s(stmt->dbc) && !no_information_schema(stmt)) { return i_s_procedure_columns(hstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szProcName, cbProcName, szColumnName, cbColumnName); } else { return mysql_procedure_columns(hstmt, szCatalogName, cbCatalogName, szSchemaName, cbSchemaName, szProcName, cbProcName, szColumnName, cbColumnName); } }