diff options
Diffstat (limited to 'sql/sql_parse.cc')
-rw-r--r-- | sql/sql_parse.cc | 3404 |
1 files changed, 2390 insertions, 1014 deletions
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 7e68db0dcd2..58b125b3ddc 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -21,7 +21,6 @@ #include <m_ctype.h> #include <myisam.h> #include <my_dir.h> -#include <assert.h> #ifdef HAVE_INNOBASE_DB #include "ha_innodb.h" @@ -44,28 +43,20 @@ #else #define MIN_HANDSHAKE_SIZE 6 #endif /* HAVE_OPENSSL */ -#define SCRAMBLE_LENGTH 8 -extern int yyparse(void); -extern "C" pthread_mutex_t THR_LOCK_keycache; #ifdef SOLARIS extern "C" int gethostname(char *name, int namelen); #endif -static int check_for_max_user_connections(USER_CONN *uc); +#ifndef NO_EMBEDDED_ACCESS_CHECKS +static int check_for_max_user_connections(THD *thd, USER_CONN *uc); +#endif static void decrease_user_connections(USER_CONN *uc); static bool check_db_used(THD *thd,TABLE_LIST *tables); -static bool check_merge_table_access(THD *thd, char *db, TABLE_LIST *tables); -static void mysql_init_query(THD *thd); static void remove_escape(char *name); static void refresh_status(void); static bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); -static bool create_total_list(THD *thd, LEX *lex, - TABLE_LIST **result, bool skip_first); -static bool check_one_table_access(THD *thd, ulong want_access, - TABLE_LIST *table, bool no_errors); - const char *any_db="*any*"; // Special symbol for check_access @@ -74,10 +65,11 @@ const char *command_name[]={ "Drop DB", "Refresh", "Shutdown", "Statistics", "Processlist", "Connect","Kill","Debug","Ping","Time","Delayed_insert","Change user", "Binlog Dump","Table Dump", "Connect Out", "Register Slave", + "Prepare", "Prepare Execute", "Long Data", "Close stmt", "Error" // Last command number }; -bool volatile abort_slave = 0; +static char empty_c_string[1]= {0}; // Used for not defined 'db' #ifdef __WIN__ static void test_signal(int sig_ptr) @@ -124,23 +116,25 @@ static bool end_active_trans(THD *thd) } +#ifdef HAVE_REPLICATION inline bool all_tables_not_ok(THD *thd, TABLE_LIST *tables) { return (table_rules_on && tables && !tables_ok(thd,tables) && - ((thd->lex.sql_command != SQLCOM_DELETE_MULTI) || - !tables_ok(thd,(TABLE_LIST *)thd->lex.auxilliary_table_list.first))); + ((thd->lex->sql_command != SQLCOM_DELETE_MULTI) || + !tables_ok(thd, + (TABLE_LIST *)thd->lex->auxilliary_table_list.first))); } +#endif static HASH hash_user_connections; -extern pthread_mutex_t LOCK_user_conn; static int get_or_create_user_conn(THD *thd, const char *user, const char *host, - USER_RESOURCES *mqh) + USER_RESOURCES *mqh) { int return_val=0; - uint temp_len, user_len, host_len; + uint temp_len, user_len; char temp_user[USERNAME_LENGTH+HOSTNAME_LENGTH+2]; struct user_conn *uc; @@ -148,7 +142,6 @@ static int get_or_create_user_conn(THD *thd, const char *user, DBUG_ASSERT(host != 0); user_len=strlen(user); - host_len=strlen(host); temp_len= (strmov(strmov(temp_user, user)+1, host) - temp_user)+1; (void) pthread_mutex_lock(&LOCK_user_conn); if (!(uc = (struct user_conn *) hash_search(&hash_user_connections, @@ -159,7 +152,7 @@ static int get_or_create_user_conn(THD *thd, const char *user, my_malloc(sizeof(struct user_conn) + temp_len+1, MYF(MY_WME))))) { - send_error(¤t_thd->net, 0, NullS); // Out of memory + send_error(thd, 0, NullS); // Out of memory return_val=1; goto end; } @@ -168,16 +161,16 @@ static int get_or_create_user_conn(THD *thd, const char *user, uc->user_len= user_len; uc->host=uc->user + uc->user_len + 1; uc->len = temp_len; - uc->connections = 0; + uc->connections = 1; uc->questions=uc->updates=uc->conn_per_hour=0; uc->user_resources=*mqh; - if (max_user_connections && mqh->connections > max_user_connections) + if (max_user_connections && mqh->connections > max_user_connections) uc->user_resources.connections = max_user_connections; uc->intime=thd->thr_create_time; - if (hash_insert(&hash_user_connections, (byte*) uc)) + if (my_hash_insert(&hash_user_connections, (byte*) uc)) { my_free((char*) uc,0); - send_error(¤t_thd->net, 0, NullS); // Out of memory + send_error(thd, 0, NullS); // Out of memory return_val=1; goto end; } @@ -187,104 +180,208 @@ static int get_or_create_user_conn(THD *thd, const char *user, end: (void) pthread_mutex_unlock(&LOCK_user_conn); return return_val; - + } /* - Check if user is ok - Updates: - thd->{user,master_access,priv_user,priv_host,db,db_access} + Check if user exist and password supplied is correct. + SYNOPSIS + check_user() + thd thread handle, thd->{host,user,ip} are used + command originator of the check: now check_user is called + during connect and change user procedures; used for + logging. + passwd scrambled password recieved from client + passwd_len length of scrambled password + db database name to connect to, may be NULL + check_count dont know exactly + + Note, that host, user and passwd may point to communication buffer. + Current implementation does not depened on that, but future changes + should be done with this in mind; 'thd' is INOUT, all other params + are 'IN'. + + RETURN VALUE + 0 OK; thd->user, thd->master_access, thd->priv_user, thd->db and + thd->db_access are updated; OK is sent to client; + -1 access denied or handshake error; error is sent to client; + >0 error, not sent to client */ -static bool check_user(THD *thd,enum_server_command command, const char *user, - const char *passwd, const char *db, bool check_count) +int check_user(THD *thd, enum enum_server_command command, + const char *passwd, uint passwd_len, const char *db, + bool check_count) { - NET *net= &thd->net; - thd->db=0; - thd->db_length=0; - USER_RESOURCES ur; - char tmp_passwd[SCRAMBLE_LENGTH + 1]; + DBUG_ENTER("check_user"); + +#ifdef NO_EMBEDDED_ACCESS_CHECKS + thd->master_access= GLOBAL_ACLS; // Full rights + /* Change database if necessary: OK or FAIL is sent in mysql_change_db */ + if (db && db[0]) + { + thd->db= 0; + thd->db_length= 0; + if (mysql_change_db(thd, db)) + { + if (thd->user_connect) + decrease_user_connections(thd->user_connect); + DBUG_RETURN(-1); + } + } + else + send_ok(thd); + DBUG_RETURN(0); +#else - if (passwd[0] && strlen(passwd) != SCRAMBLE_LENGTH) - return 1; + my_bool opt_secure_auth_local; + pthread_mutex_lock(&LOCK_global_system_variables); + opt_secure_auth_local= opt_secure_auth; + pthread_mutex_unlock(&LOCK_global_system_variables); + /* - Move password to temporary buffer as it may be stored in communication - buffer + If the server is running in secure auth mode, short scrambles are + forbidden. */ - strmov(tmp_passwd, passwd); - passwd= tmp_passwd; // Use local copy + if (opt_secure_auth_local && passwd_len == SCRAMBLE_LENGTH_323) + { + net_printf(thd, ER_NOT_SUPPORTED_AUTH_MODE); + mysql_log.write(thd, COM_CONNECT, ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN(-1); + } + if (passwd_len != 0 && + passwd_len != SCRAMBLE_LENGTH && + passwd_len != SCRAMBLE_LENGTH_323) + DBUG_RETURN(ER_HANDSHAKE_ERROR); - if (!(thd->user = my_strdup(user, MYF(0)))) + /* + Clear thd->db as it points to something, that will be freed when + connection is closed. We don't want to accidently free a wrong pointer + if connect failed. Also in case of 'CHANGE USER' failure, current + database will be switched to 'no database selected'. + */ + thd->db= 0; + thd->db_length= 0; + + USER_RESOURCES ur; + int res= acl_getroot(thd, &ur, passwd, passwd_len); +#ifndef EMBEDDED_LIBRARY + if (res == -1) { - send_error(net,ER_OUT_OF_RESOURCES); - return 1; + /* + This happens when client (new) sends password scrambled with + scramble(), but database holds old value (scrambled with + scramble_323()). Here we please client to send scrambled_password + in old format. + */ + NET *net= &thd->net; + if (opt_secure_auth_local) + { + net_printf(thd, ER_SERVER_IS_IN_SECURE_AUTH_MODE, + thd->user, thd->host_or_ip); + mysql_log.write(thd, COM_CONNECT, ER(ER_SERVER_IS_IN_SECURE_AUTH_MODE), + thd->user, thd->host_or_ip); + DBUG_RETURN(-1); + } + if (send_old_password_request(thd) || + my_net_read(net) != SCRAMBLE_LENGTH_323 + 1) // We have to read very + { // specific packet size + inc_host_errors(&thd->remote.sin_addr); + DBUG_RETURN(ER_HANDSHAKE_ERROR); + } + /* Final attempt to check the user based on reply */ + /* So as passwd is short, errcode is always >= 0 */ + res= acl_getroot(thd, &ur, (char *) net->read_pos, SCRAMBLE_LENGTH_323); } - thd->master_access=acl_getroot(thd, thd->host, thd->ip, thd->user, - passwd, thd->scramble, - &thd->priv_user, thd->priv_host, - protocol_version == 9 || - !(thd->client_capabilities & - CLIENT_LONG_PASSWORD),&ur); - DBUG_PRINT("info", - ("Capabilities: %d packet_length: %ld Host: '%s' Login user: '%s' Priv_user: '%s' Using password: %s Access: %u db: '%s'", - thd->client_capabilities, thd->max_client_packet_length, - thd->host_or_ip, thd->user, thd->priv_user, - passwd[0] ? "yes": "no", - thd->master_access, thd->db ? thd->db : "*none*")); - if (thd->master_access & NO_ACCESS) - { - net_printf(net, ER_ACCESS_DENIED_ERROR, - thd->user, - thd->host_or_ip, - passwd[0] ? ER(ER_YES) : ER(ER_NO)); - mysql_log.write(thd,COM_CONNECT,ER(ER_ACCESS_DENIED_ERROR), - thd->user, - thd->host_or_ip, - passwd[0] ? ER(ER_YES) : ER(ER_NO)); - return(1); // Error already given - } - if (check_count) - { - VOID(pthread_mutex_lock(&LOCK_thread_count)); - bool tmp=(thread_count - delayed_insert_threads >= max_connections && - !(thd->master_access & SUPER_ACL)); - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - if (tmp) - { // Too many connections - send_error(net, ER_CON_COUNT_ERROR); - return(1); - } - } - mysql_log.write(thd,command, - (thd->priv_user == thd->user ? - (char*) "%s@%s on %s" : - (char*) "%s@%s as anonymous on %s"), - user, - thd->host_or_ip, - db ? db : (char*) ""); - thd->db_access=0; - /* Don't allow user to connect if he has done too many queries */ - if ((ur.questions || ur.updates || ur.connections || max_user_connections) && - get_or_create_user_conn(thd,user,thd->host_or_ip,&ur)) - return -1; - if (thd->user_connect && (thd->user_connect->user_resources.connections || - max_user_connections) && - check_for_max_user_connections(thd->user_connect)) - return -1; - if (db && db[0]) +#endif /*EMBEDDED_LIBRARY*/ + /* here res is always >= 0 */ + if (res == 0) { - bool error=test(mysql_change_db(thd,db)); - if (error && thd->user_connect) - decrease_user_connections(thd->user_connect); - return error; + if (!(thd->master_access & NO_ACCESS)) // authentification is OK + { + DBUG_PRINT("info", + ("Capabilities: %d packet_length: %ld Host: '%s' " + "Login user: '%s' Priv_user: '%s' Using password: %s " + "Access: %u db: '%s'", + thd->client_capabilities, thd->max_client_packet_length, + thd->host_or_ip, thd->user, thd->priv_user, + passwd_len ? "yes": "no", + thd->master_access, thd->db ? thd->db : "*none*")); + + if (check_count) + { + VOID(pthread_mutex_lock(&LOCK_thread_count)); + bool count_ok= thread_count < max_connections + delayed_insert_threads + || (thd->master_access & SUPER_ACL); + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + if (!count_ok) + { // too many connections + send_error(thd, ER_CON_COUNT_ERROR); + DBUG_RETURN(-1); + } + } + + /* Why logging is performed before all checks've passed? */ + mysql_log.write(thd,command, + (thd->priv_user == thd->user ? + (char*) "%s@%s on %s" : + (char*) "%s@%s as anonymous on %s"), + thd->user, thd->host_or_ip, + db ? db : (char*) ""); + + /* + This is the default access rights for the current database. It's + set to 0 here because we don't have an active database yet (and we + may not have an active database to set. + */ + thd->db_access=0; + + /* Don't allow user to connect if he has done too many queries */ + if ((ur.questions || ur.updates || ur.connections || + max_user_connections) && + get_or_create_user_conn(thd,thd->user,thd->host_or_ip,&ur)) + DBUG_RETURN(-1); + if (thd->user_connect && + (thd->user_connect->user_resources.connections || + max_user_connections) && + check_for_max_user_connections(thd, thd->user_connect)) + DBUG_RETURN(-1); + + /* Change database if necessary: OK or FAIL is sent in mysql_change_db */ + if (db && db[0]) + { + if (mysql_change_db(thd, db)) + { + if (thd->user_connect) + decrease_user_connections(thd->user_connect); + DBUG_RETURN(-1); + } + } + else + send_ok(thd); + thd->password= test(passwd_len); // remember for error messages + /* Ready to handle queries */ + DBUG_RETURN(0); + } + } + else if (res == 2) // client gave short hash, server has long hash + { + net_printf(thd, ER_NOT_SUPPORTED_AUTH_MODE); + mysql_log.write(thd,COM_CONNECT,ER(ER_NOT_SUPPORTED_AUTH_MODE)); + DBUG_RETURN(-1); } - send_ok(net); // Ready to handle questions - thd->password= test(passwd[0]); // Remember for error messages - return 0; // ok + net_printf(thd, ER_ACCESS_DENIED_ERROR, + thd->user, + thd->host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + mysql_log.write(thd, COM_CONNECT, ER(ER_ACCESS_DENIED_ERROR), + thd->user, + thd->host_or_ip, + passwd_len ? ER(ER_YES) : ER(ER_NO)); + DBUG_RETURN(-1); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ } - /* Check for maximum allowable user connections, if the mysqld server is started with corresponding variable that is greater then 0. @@ -302,44 +399,83 @@ extern "C" void free_user(struct user_conn *uc) my_free((char*) uc,MYF(0)); } -void init_max_user_conn(void) +void init_max_user_conn(void) { - (void) hash_init(&hash_user_connections,max_connections,0,0, + (void) hash_init(&hash_user_connections,system_charset_info,max_connections, + 0,0, (hash_get_key) get_key_conn, (hash_free_key) free_user, 0); } -static int check_for_max_user_connections(USER_CONN *uc) +/* + check if user has already too many connections + + SYNOPSIS + check_for_max_user_connections() + thd Thread handle + uc User connect object + + NOTES + If check fails, we decrease user connection count, which means one + shouldn't call decrease_user_connections() after this function. + + RETURN + 0 ok + 1 error +*/ + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + +static int check_for_max_user_connections(THD *thd, USER_CONN *uc) { int error=0; DBUG_ENTER("check_for_max_user_connections"); (void) pthread_mutex_lock(&LOCK_user_conn); if (max_user_connections && - max_user_connections < uc->connections) + max_user_connections < (uint) uc->connections) { - net_printf(&(current_thd->net),ER_TOO_MANY_USER_CONNECTIONS, uc->user); + net_printf(thd,ER_TOO_MANY_USER_CONNECTIONS, uc->user); error=1; goto end; } if (uc->user_resources.connections && uc->user_resources.connections <= uc->conn_per_hour) { - net_printf(¤t_thd->net, ER_USER_LIMIT_REACHED, uc->user, + net_printf(thd, ER_USER_LIMIT_REACHED, uc->user, "max_connections", (long) uc->user_resources.connections); error=1; goto end; } uc->conn_per_hour++; -end: + + end: if (error) uc->connections--; // no need for decrease_user_connections() here (void) pthread_mutex_unlock(&LOCK_user_conn); DBUG_RETURN(error); } +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + +/* + Decrease user connection count + + SYNOPSIS + decrease_user_connections() + uc User connection object + NOTES + If there is a n user connection object for a connection + (which only happens if 'max_user_connections' is defined or + if someone has created a resource grant for a user), then + the connection count is always incremented on connect. + + The user connect object is not freed if some users has + 'max connections per hour' defined as we need to be able to hold + count over the lifetime of the connection. +*/ static void decrease_user_connections(USER_CONN *uc) { @@ -390,9 +526,13 @@ void init_update_queries(void) uc_update_queries[SQLCOM_RESTORE_TABLE]=1; uc_update_queries[SQLCOM_DELETE_MULTI]=1; uc_update_queries[SQLCOM_DROP_INDEX]=1; - uc_update_queries[SQLCOM_MULTI_UPDATE]=1; + uc_update_queries[SQLCOM_UPDATE_MULTI]=1; } +bool is_update_query(enum enum_sql_command command) +{ + return uc_update_queries[command]; +} /* Check if maximum queries per hour limit has been reached @@ -406,6 +546,9 @@ void init_update_queries(void) static bool check_mqh(THD *thd, uint check_command) { +#ifdef NO_EMBEDDED_ACCESS_CHECKS + return(0); +#else bool error=0; time_t check_time = thd->start_time ? thd->start_time : time(NULL); USER_CONN *uc=thd->user_connect; @@ -426,7 +569,7 @@ static bool check_mqh(THD *thd, uint check_command) if (uc->user_resources.questions && uc->questions++ >= uc->user_resources.questions) { - net_printf(&thd->net, ER_USER_LIMIT_REACHED, uc->user, "max_questions", + net_printf(thd, ER_USER_LIMIT_REACHED, uc->user, "max_questions", (long) uc->user_resources.questions); error=1; goto end; @@ -437,7 +580,7 @@ static bool check_mqh(THD *thd, uint check_command) if (uc->user_resources.updates && uc_update_queries[check_command] && uc->updates++ >= uc->user_resources.updates) { - net_printf(&thd->net, ER_USER_LIMIT_REACHED, uc->user, "max_updates", + net_printf(thd, ER_USER_LIMIT_REACHED, uc->user, "max_updates", (long) uc->user_resources.updates); error=1; goto end; @@ -445,14 +588,15 @@ static bool check_mqh(THD *thd, uint check_command) } end: DBUG_RETURN(error); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ } static void reset_mqh(THD *thd, LEX_USER *lu, bool get_them= 0) { - +#ifndef NO_EMBEDDED_ACCESS_CHECKS (void) pthread_mutex_lock(&LOCK_user_conn); - if (lu) // for GRANT + if (lu) // for GRANT { USER_CONN *uc; uint temp_len=lu->user.length+lu->host.length+2; @@ -474,7 +618,8 @@ static void reset_mqh(THD *thd, LEX_USER *lu, bool get_them= 0) { for (uint idx=0;idx < hash_user_connections.records; idx++) { - USER_CONN *uc=(struct user_conn *) hash_element(&hash_user_connections, idx); + USER_CONN *uc=(struct user_conn *) hash_element(&hash_user_connections, + idx); if (get_them) get_mqh(uc->user,uc->host,uc); uc->questions=0; @@ -483,54 +628,62 @@ static void reset_mqh(THD *thd, LEX_USER *lu, bool get_them= 0) } } (void) pthread_mutex_unlock(&LOCK_user_conn); +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ } - /* - Check connnetion and get priviliges - Returns 0 on ok, -1 < if error is given > 0 on error. + Perform handshake, authorize client and update thd ACL variables. + SYNOPSIS + check_connection() + thd thread handle + + RETURN + 0 success, OK is sent to user, thd is updated. + -1 error, which is sent to user + > 0 error code (not sent to user) */ -static int -check_connections(THD *thd) +#ifndef EMBEDDED_LIBRARY +static int check_connection(THD *thd) { - uint connect_errors=0; + uint connect_errors= 0; NET *net= &thd->net; - /* Store the connection details */ - DBUG_PRINT("info", (("check_connections called by thread %d"), - thd->thread_id)); - DBUG_PRINT("info",("New connection received on %s", - vio_description(net->vio))); + + DBUG_PRINT("info", + ("New connection received on %s", vio_description(net->vio))); + if (!thd->host) // If TCP/IP connection { char ip[30]; if (vio_peer_addr(net->vio, ip, &thd->peer_port)) return (ER_BAD_HOST_ERROR); - if (!(thd->ip = my_strdup(ip,MYF(0)))) + if (!(thd->ip= my_strdup(ip,MYF(0)))) return (ER_OUT_OF_RESOURCES); - thd->host_or_ip=thd->ip; + thd->host_or_ip= thd->ip; #if !defined(HAVE_SYS_UN_H) || defined(HAVE_mit_thread) /* Fast local hostname resolve for Win32 */ if (!strcmp(thd->ip,"127.0.0.1")) { - thd->host= (char*) localhost; - thd->host_or_ip= localhost; + thd->host= (char*) my_localhost; + thd->host_or_ip= my_localhost; } else #endif - if (!(specialflag & SPECIAL_NO_RESOLVE)) { - vio_in_addr(net->vio,&thd->remote.sin_addr); - thd->host=ip_to_hostname(&thd->remote.sin_addr,&connect_errors); - /* Cut very long hostnames to avoid possible overflows */ - if (thd->host) + if (!(specialflag & SPECIAL_NO_RESOLVE)) { - thd->host[min(strlen(thd->host), HOSTNAME_LENGTH)]= 0; - thd->host_or_ip= thd->host; + vio_in_addr(net->vio,&thd->remote.sin_addr); + thd->host=ip_to_hostname(&thd->remote.sin_addr,&connect_errors); + /* Cut very long hostnames to avoid possible overflows */ + if (thd->host) + { + thd->host[min(strlen(thd->host), HOSTNAME_LENGTH)]= 0; + thd->host_or_ip= thd->host; + } + if (connect_errors > max_connect_errors) + return(ER_HOST_IS_BLOCKED); } - if (connect_errors > max_connect_errors) - return(ER_HOST_IS_BLOCKED); } DBUG_PRINT("info",("Host: %s ip: %s", thd->host ? thd->host : "unknown host", @@ -543,15 +696,16 @@ check_connections(THD *thd) DBUG_PRINT("info",("Host: %s",thd->host)); thd->host_or_ip= thd->host; thd->ip= 0; - bzero((char*) &thd->remote,sizeof(struct sockaddr)); + bzero((char*) &thd->remote, sizeof(struct sockaddr)); } vio_keepalive(net->vio, TRUE); - - ulong pkt_len=0; + ulong pkt_len= 0; + char *end; { /* buff[] needs to big enough to hold the server_version variable */ - char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH+32],*end; - int client_flags = CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB; + char buff[SERVER_VERSION_LENGTH + SCRAMBLE_LENGTH + 64]; + ulong client_flags = (CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION); if (opt_using_transactions) client_flags|=CLIENT_TRANSACTIONS; @@ -563,19 +717,36 @@ check_connections(THD *thd) client_flags |= CLIENT_SSL; /* Wow, SSL is avalaible! */ #endif /* HAVE_OPENSSL */ - end=strnmov(buff,server_version,SERVER_VERSION_LENGTH)+1; - int4store((uchar*) end,thd->thread_id); - end+=4; - memcpy(end,thd->scramble,SCRAMBLE_LENGTH+1); - end+=SCRAMBLE_LENGTH +1; - int2store(end,client_flags); - end[2]=(char) MY_CHARSET_CURRENT; - int2store(end+3,thd->server_status); - bzero(end+5,13); - end+=18; - if (net_write_command(net,(uchar) protocol_version, buff, + end= strnmov(buff, server_version, SERVER_VERSION_LENGTH) + 1; + int4store((uchar*) end, thd->thread_id); + end+= 4; + /* + So as check_connection is the only entry point to authorization + procedure, scramble is set here. This gives us new scramble for + each handshake. + */ + create_random_string(thd->scramble, SCRAMBLE_LENGTH, &thd->rand); + /* + Old clients does not understand long scrambles, but can ignore packet + tail: that's why first part of the scramble is placed here, and second + part at the end of packet. + */ + end= strmake(end, thd->scramble, SCRAMBLE_LENGTH_323) + 1; + + int2store(end, client_flags); + /* write server characteristics: up to 16 bytes allowed */ + end[2]=(char) default_charset_info->number; + int2store(end+3, thd->server_status); + bzero(end+5, 13); + end+= 18; + /* write scramble tail */ + end= strmake(end, thd->scramble + SCRAMBLE_LENGTH_323, + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323) + 1; + + /* At this point we write connection message and read reply */ + if (net_write_command(net, (uchar) protocol_version, "", 0, buff, (uint) (end-buff)) || - (pkt_len= my_net_read(net)) == packet_error || + (pkt_len= my_net_read(net)) == packet_error || pkt_len < MIN_HANDSHAKE_SIZE) { inc_host_errors(&thd->remote.sin_addr); @@ -591,8 +762,58 @@ check_connections(THD *thd) return(ER_OUT_OF_RESOURCES); thd->client_capabilities=uint2korr(net->read_pos); +#ifdef TO_BE_REMOVED_IN_4_1_RELEASE + /* + This is just a safety check against any client that would use the old + CLIENT_CHANGE_USER flag + */ + if ((thd->client_capabilities & CLIENT_PROTOCOL_41) && + !(thd->client_capabilities & (CLIENT_RESERVED | + CLIENT_SECURE_CONNECTION | + CLIENT_MULTI_RESULTS))) + thd->client_capabilities&= ~CLIENT_PROTOCOL_41; +#endif + if (thd->client_capabilities & CLIENT_PROTOCOL_41) + { + thd->client_capabilities|= ((ulong) uint2korr(net->read_pos+2)) << 16; + thd->max_client_packet_length= uint4korr(net->read_pos+4); + DBUG_PRINT("info", ("client_character_set: %d", (uint) net->read_pos[8])); + /* + Use server character set and collation if + - client has not specified a character set + - client character set is the same as the servers + - client character set doesn't exists in server + */ + if (!(thd->variables.character_set_client= + get_charset((uint) net->read_pos[8], MYF(0))) || + !my_strcasecmp(&my_charset_latin1, + global_system_variables.character_set_client->name, + thd->variables.character_set_client->name)) + { + thd->variables.character_set_client= + global_system_variables.character_set_client; + thd->variables.collation_connection= + global_system_variables.collation_connection; + thd->variables.character_set_results= + global_system_variables.character_set_results; + } + else + { + thd->variables.character_set_results= + thd->variables.collation_connection= + thd->variables.character_set_client; + } + thd->update_charset(); + end= (char*) net->read_pos+32; + } + else + { + thd->max_client_packet_length= uint3korr(net->read_pos+2); + end= (char*) net->read_pos+5; + } + if (thd->client_capabilities & CLIENT_IGNORE_SPACE) - thd->sql_mode|= MODE_IGNORE_SPACE; + thd->variables.sql_mode|= MODE_IGNORE_SPACE; #ifdef HAVE_OPENSSL DBUG_PRINT("info", ("client capabilities: %d", thd->client_capabilities)); if (thd->client_capabilities & CLIENT_SSL) @@ -609,10 +830,10 @@ check_connections(THD *thd) DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", pkt_len)); inc_host_errors(&thd->remote.sin_addr); - return(ER_HANDSHAKE_ERROR); + return(ER_HANDSHAKE_ERROR); } DBUG_PRINT("info", ("Reading user information over SSL layer")); - if ((pkt_len=my_net_read(net)) == packet_error || + if ((pkt_len= my_net_read(net)) == packet_error || pkt_len < NORMAL_HANDSHAKE_SIZE) { DBUG_PRINT("error", ("Failed to read user information (pkt_len= %lu)", @@ -621,32 +842,89 @@ check_connections(THD *thd) return(ER_HANDSHAKE_ERROR); } } - else +#endif + + if (end >= (char*) net->read_pos+ pkt_len +2) { - DBUG_PRINT("info", ("Leaving IO layer intact")); - if (pkt_len < NORMAL_HANDSHAKE_SIZE) - { - inc_host_errors(&thd->remote.sin_addr); - return ER_HANDSHAKE_ERROR; - } + inc_host_errors(&thd->remote.sin_addr); + return(ER_HANDSHAKE_ERROR); } -#endif - thd->max_client_packet_length=uint3korr(net->read_pos+2); - char *user= (char*) net->read_pos+5; - char *passwd= strend(user)+1; - char *db=0; - if (thd->client_capabilities & CLIENT_CONNECT_WITH_DB) - db=strend(passwd)+1; if (thd->client_capabilities & CLIENT_INTERACTIVE) thd->variables.net_wait_timeout= thd->variables.net_interactive_timeout; if ((thd->client_capabilities & CLIENT_TRANSACTIONS) && opt_using_transactions) - thd->net.return_status= &thd->server_status; + net->return_status= &thd->server_status; net->read_timeout=(uint) thd->variables.net_read_timeout; - if (check_user(thd,COM_CONNECT, user, passwd, db, 1)) - return (-1); - return 0; + + char *user= end; + char *passwd= strend(user)+1; + char *db= passwd; + char db_buff[NAME_LEN+1]; // buffer to store db in utf8 + char user_buff[USERNAME_LENGTH+1]; // buffer to store user in utf8 + /* + Old clients send null-terminated string as password; new clients send + the size (1 byte) + string (not null-terminated). Hence in case of empty + password both send '\0'. + */ + uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? + *passwd++ : strlen(passwd); + db= thd->client_capabilities & CLIENT_CONNECT_WITH_DB ? + db + passwd_len + 1 : 0; + + /* Since 4.1 all database names are stored in utf8 */ + if (db) + { + db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, + system_charset_info, + db, strlen(db), + thd->charset())]= 0; + db= db_buff; + } + + if (user) + { + user_buff[copy_and_convert(user_buff, sizeof(user_buff)-1, + system_charset_info, user, strlen(user), + thd->charset())]= '\0'; + user= user_buff; + } + + if (thd->user) + x_free(thd->user); + if (!(thd->user= my_strdup(user, MYF(0)))) + return (ER_OUT_OF_RESOURCES); + return check_user(thd, COM_CONNECT, passwd, passwd_len, db, true); +} + + +void execute_init_command(THD *thd, sys_var_str *init_command_var, + rw_lock_t *var_mutex) +{ + Vio* save_vio; + ulong save_client_capabilities; + + thd->proc_info= "Execution of init_command"; + /* + We need to lock init_command_var because + during execution of init_command_var query + values of init_command_var can't be changed + */ + rw_rdlock(var_mutex); + thd->query= init_command_var->value; + thd->query_length= init_command_var->value_length; + save_client_capabilities= thd->client_capabilities; + thd->client_capabilities|= CLIENT_MULTI_QUERIES; + /* + We don't need return result of execution to client side. + To forbid this we should set thd->net.vio to 0. + */ + save_vio= thd->net.vio; + thd->net.vio= 0; + dispatch_command(COM_QUERY, thd, thd->query, thd->query_length+1); + rw_unlock(var_mutex); + thd->client_capabilities= save_client_capabilities; + thd->net.vio= save_vio; } @@ -664,7 +942,7 @@ pthread_handler_decl(handle_one_connection,arg) // The following calls needs to be done before we call DBUG_ macros if (!(test_flags & TEST_NO_THREADS) & my_thread_init()) { - close_connection(&thd->net,ER_OUT_OF_RESOURCES); + close_connection(thd, ER_OUT_OF_RESOURCES, 1); statistic_increment(aborted_connects,&LOCK_status); end_thread(thd,0); return 0; @@ -691,7 +969,7 @@ pthread_handler_decl(handle_one_connection,arg) #endif if (thd->store_globals()) { - close_connection(&thd->net,ER_OUT_OF_RESOURCES); + close_connection(thd, ER_OUT_OF_RESOURCES, 1); statistic_increment(aborted_connects,&LOCK_status); end_thread(thd,0); return 0; @@ -703,13 +981,13 @@ pthread_handler_decl(handle_one_connection,arg) NET *net= &thd->net; thd->thread_stack= (char*) &thd; - if ((error=check_connections(thd))) + if ((error=check_connection(thd))) { // Wrong permissions if (error > 0) - net_printf(net,error,thd->host_or_ip); + net_printf(thd,error,thd->host_or_ip); #ifdef __NT__ if (vio_type(net->vio) == VIO_TYPE_NAMEDPIPE) - sleep(1); /* must wait after eof() */ + my_sleep(1000); /* must wait after eof() */ #endif statistic_increment(aborted_connects,&LOCK_status); goto end_thread; @@ -722,16 +1000,17 @@ pthread_handler_decl(handle_one_connection,arg) if (thd->client_capabilities & CLIENT_COMPRESS) net->compress=1; // Use compression - thd->proc_info=0; // Remove 'login' - thd->command=COM_SLEEP; - thd->version=refresh_version; - thd->set_time(); - init_sql_alloc(&thd->mem_root, thd->variables.query_alloc_block_size, - thd->variables.query_prealloc_size); - init_sql_alloc(&thd->transaction.mem_root, - thd->variables.trans_alloc_block_size, - thd->variables.trans_prealloc_size); + thd->version= refresh_version; + if (sys_init_connect.value_length && !(thd->master_access & SUPER_ACL)) + { + execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); + if (thd->query_error) + thd->killed= 1; + } + thd->proc_info=0; + thd->set_time(); + thd->init_for_queries(); while (!net->error && net->vio != 0 && !thd->killed) { if (do_command(thd)) @@ -740,7 +1019,7 @@ pthread_handler_decl(handle_one_connection,arg) if (thd->user_connect) decrease_user_connections(thd->user_connect); free_root(&thd->mem_root,MYF(0)); - if (net->error && net->vio != 0) + if (net->error && net->vio != 0 && net->report_error) { if (!thd->killed && thd->variables.log_warnings) sql_print_error(ER(ER_NEW_ABORTING_CONNECTION), @@ -749,7 +1028,7 @@ pthread_handler_decl(handle_one_connection,arg) thd->host_or_ip, (net->last_errno ? ER(net->last_errno) : ER(ER_UNKNOWN_ERROR))); - send_error(net,net->last_errno,NullS); + send_error(thd,net->last_errno,NullS); statistic_increment(aborted_threads,&LOCK_status); } else if (thd->killed) @@ -758,7 +1037,7 @@ pthread_handler_decl(handle_one_connection,arg) } end_thread: - close_connection(net); + close_connection(thd, 0, 1); end_thread(thd,1); /* If end_thread returns, we are either running with --one-thread @@ -770,6 +1049,8 @@ end_thread: return(0); /* purecov: deadcode */ } +#endif /* EMBEDDED_LIBRARY */ + /* Execute commands from bootstrap_file. Used when creating the initial grant tables @@ -784,12 +1065,15 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg) /* The following must be called before DBUG_ENTER */ if (my_thread_init() || thd->store_globals()) { - close_connection(&thd->net,ER_OUT_OF_RESOURCES); - thd->fatal_error=1; +#ifndef EMBEDDED_LIBRARY + close_connection(thd, ER_OUT_OF_RESOURCES, 1); +#endif + thd->fatal_error(); goto end; } DBUG_ENTER("handle_bootstrap"); +#ifndef EMBEDDED_LIBRARY pthread_detach_this_thread(); thd->thread_stack= (char*) &thd; #if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) @@ -797,6 +1081,7 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg) VOID(sigemptyset(&set)); // Get mask in use VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals)); #endif +#endif /* EMBEDDED_LIBRARY */ if (thd->variables.max_join_size == HA_POS_ERROR) thd->options |= OPTION_BIG_SELECTS; @@ -806,18 +1091,20 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg) thd->priv_user=thd->user=(char*) my_strdup("boot", MYF(MY_WME)); buff= (char*) thd->net.buff; - init_sql_alloc(&thd->mem_root, thd->variables.query_alloc_block_size, - thd->variables.query_prealloc_size); - init_sql_alloc(&thd->transaction.mem_root, - thd->variables.trans_alloc_block_size, - thd->variables.trans_prealloc_size); + thd->init_for_queries(); while (fgets(buff, thd->net.max_packet, file)) { uint length=(uint) strlen(buff); - while (length && (isspace(buff[length-1]) || buff[length-1] == ';')) + if (buff[length-1]!='\n' && !feof(file)) + { + send_error(thd,ER_NET_PACKET_TOO_LARGE, NullS); + thd->is_fatal_error= 1; + break; + } + while (length && (my_isspace(thd->charset(), buff[length-1]) || + buff[length-1] == ';')) length--; buff[length]=0; - thd->current_tablenr=0; thd->query_length=length; thd->query= thd->memdup_w_gap(buff, length+1, thd->db_length+1); thd->query[length] = '\0'; @@ -831,7 +1118,7 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg) } mysql_parse(thd,thd->query,length); close_thread_tables(thd); // Free tables - if (thd->fatal_error) + if (thd->is_fatal_error) break; free_root(&thd->mem_root,MYF(MY_KEEP_PREALLOC)); free_root(&thd->transaction.mem_root,MYF(MY_KEEP_PREALLOC)); @@ -839,21 +1126,31 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg) /* thd->fatal_error should be set in case something went wrong */ end: +#ifndef EMBEDDED_LIBRARY (void) pthread_mutex_lock(&LOCK_thread_count); thread_count--; (void) pthread_mutex_unlock(&LOCK_thread_count); (void) pthread_cond_broadcast(&COND_thread_count); my_thread_end(); pthread_exit(0); +#endif DBUG_RETURN(0); // Never reached } + /* This works because items are allocated with sql_alloc() */ -inline void free_items(THD *thd) +void free_items(Item *item) { + for (; item ; item=item->next) + item->delete_self(); +} + /* This works because items are allocated with sql_alloc() */ - for (Item *item=thd->free_list ; item ; item=item->next) - delete item; + +void cleanup_items(Item *item) +{ + for (; item ; item=item->next) + item->cleanup(); } int mysql_table_dump(THD* thd, char* db, char* tbl_name, int fd) @@ -872,24 +1169,24 @@ int mysql_table_dump(THD* thd, char* db, char* tbl_name, int fd) if (!db || check_db_name(db)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, db ? db : "NULL"); + net_printf(thd,ER_WRONG_DB_NAME, db ? db : "NULL"); goto err; } if (lower_case_table_names) - casedn_str(tbl_name); - remove_escape(tbl_name); + my_casedn_str(files_charset_info, tbl_name); + remove_escape(table_list->real_name); if (!(table=open_ltable(thd, table_list, TL_READ_NO_INSERT))) DBUG_RETURN(1); - if (check_one_table_access(thd, SELECT_ACL, table_list, 0)) + if (check_one_table_access(thd, SELECT_ACL, table_list)) goto err; thd->free_list = 0; thd->query_length=(uint) strlen(tbl_name); thd->query = tbl_name; if ((error = mysqld_dump_create_info(thd, table, -1))) { - my_error(ER_GET_ERRNO, MYF(0)); + my_error(ER_GET_ERRNO, MYF(0), my_errno); goto err; } net_flush(&thd->net); @@ -902,7 +1199,17 @@ err: } - /* Execute one command from socket (query or simple command) */ +#ifndef EMBEDDED_LIBRARY + +/* + Read one command from socket and execute it (query or simple command). + This function is called in loop from thread function. + SYNOPSIS + do_command() + RETURN VALUE + 0 success + 1 request of thread shutdown (see dispatch_command() description) +*/ bool do_command(THD *thd) { @@ -914,14 +1221,17 @@ bool do_command(THD *thd) DBUG_ENTER("do_command"); net= &thd->net; - thd->current_tablenr=0; + /* + indicator of uninitialized lex => normal flow of errors handling + (see my_message_sql) + */ + thd->lex->current_select= 0; packet=0; old_timeout=net->read_timeout; // Wait max for 8 hours net->read_timeout=(uint) thd->variables.net_wait_timeout; - net->last_error[0]=0; // Clear error message - net->last_errno=0; + thd->clear_error(); // Clear error message net_new_transaction(net); if ((packet_length=my_net_read(net)) == packet_error) @@ -935,7 +1245,7 @@ bool do_command(THD *thd) statistic_increment(aborted_threads,&LOCK_status); DBUG_RETURN(TRUE); // We have to close it. } - send_error(net,net->last_errno,NullS); + send_error(thd,net->last_errno,NullS); net->error= 0; DBUG_RETURN(FALSE); } @@ -950,23 +1260,48 @@ bool do_command(THD *thd) command_name[command])); } net->read_timeout=old_timeout; // restore it + /* + packet_length contains length of data, as it was stored in packet + header. In case of malformed header, packet_length can be zero. + If packet_length is not zero, my_net_read ensures that this number + of bytes was actually read from network. Additionally my_net_read + sets packet[packet_length]= 0 (thus if packet_length == 0, + command == packet[0] == COM_SLEEP). + In dispatch_command packet[packet_length] points beyond the end of packet. + */ DBUG_RETURN(dispatch_command(command,thd, packet+1, (uint) packet_length)); } +#endif /* EMBEDDED_LIBRARY */ +/* + Perform one connection-level (COM_XXXX) command. + SYNOPSIS + dispatch_command() + thd connection handle + command type of command to perform + packet data for the command, packet is always null-terminated + packet_length length of packet + 1 (to show that data is + null-terminated) except for COM_SLEEP, where it + can be zero. + RETURN VALUE + 0 ok + 1 request of thread shutdown, i. e. if command is + COM_QUIT/COM_SHUTDOWN +*/ bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length) { NET *net= &thd->net; - bool error=0; + bool error= 0; + DBUG_ENTER("dispatch_command"); + + thd->command=command; /* Commands which will always take a long time should be marked with this so that they will not get logged to the slow query log */ - bool slow_command=FALSE; - DBUG_ENTER("dispatch_command"); - - thd->command=command; + thd->slow_command=FALSE; thd->set_time(); VOID(pthread_mutex_lock(&LOCK_thread_count)); thd->query_id=query_id; @@ -975,112 +1310,194 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thread_running++; VOID(pthread_mutex_unlock(&LOCK_thread_count)); - thd->lex.select_lex.options=0; // We store status here + thd->server_status&= + ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); switch (command) { case COM_INIT_DB: + { + LEX_STRING tmp; statistic_increment(com_stat[SQLCOM_CHANGE_DB],&LOCK_status); - if (!mysql_change_db(thd,packet)) + thd->convert_string(&tmp, system_charset_info, + packet, strlen(packet), thd->charset()); + if (!mysql_change_db(thd, tmp.str)) mysql_log.write(thd,command,"%s",thd->db); break; + } +#ifdef HAVE_REPLICATION case COM_REGISTER_SLAVE: { if (!register_slave(thd, (uchar*)packet, packet_length)) - send_ok(&thd->net); + send_ok(thd); break; } +#endif case COM_TABLE_DUMP: - { - statistic_increment(com_other, &LOCK_status); - slow_command = TRUE; - uint db_len = *(uchar*)packet; - uint tbl_len = *(uchar*)(packet + db_len + 1); - char* db = thd->alloc(db_len + tbl_len + 2); - memcpy(db, packet + 1, db_len); - char* tbl_name = db + db_len; - *tbl_name++ = 0; - memcpy(tbl_name, packet + db_len + 2, tbl_len); - tbl_name[tbl_len] = 0; - if (mysql_table_dump(thd, db, tbl_name, -1)) - send_error(&thd->net); // dump to NET - break; - } + { + char *db, *tbl_name; + uint db_len= *(uchar*) packet; + uint tbl_len= *(uchar*) (packet + db_len + 1); + + statistic_increment(com_other, &LOCK_status); + thd->slow_command= TRUE; + db= thd->alloc(db_len + tbl_len + 2); + tbl_name= strmake(db, packet + 1, db_len)+1; + strmake(tbl_name, packet + db_len + 2, tbl_len); + if (mysql_table_dump(thd, db, tbl_name, -1)) + send_error(thd); // dump to NET + break; + } case COM_CHANGE_USER: { thd->change_user(); - clear_error_message(thd); // If errors from rollback + thd->clear_error(); // if errors from rollback - statistic_increment(com_other,&LOCK_status); - char *user= (char*) packet; + statistic_increment(com_other, &LOCK_status); + char *user= (char*) packet; char *passwd= strend(user)+1; - char *db= strend(passwd)+1; + /* + Old clients send null-terminated string ('\0' for empty string) for + password. New clients send the size (1 byte) + string (not null + terminated, so also '\0' for empty string). + */ + char db_buff[NAME_LEN+1]; // buffer to store db in utf8 + char *db= passwd; + uint passwd_len= thd->client_capabilities & CLIENT_SECURE_CONNECTION ? + *passwd++ : strlen(passwd); + db+= passwd_len + 1; +#ifndef EMBEDDED_LIBRARY + /* Small check for incomming packet */ + if ((uint) ((uchar*) db - net->read_pos) > packet_length) + { + send_error(thd, ER_UNKNOWN_COM_ERROR); + break; + } +#endif + /* Convert database name to utf8 */ + db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, + system_charset_info, db, strlen(db), + thd->charset())]= 0; + db= db_buff; /* Save user and privileges */ - uint save_master_access=thd->master_access; - uint save_db_access= thd->db_access; - uint save_db_length= thd->db_length; - char *save_user= thd->user; - char *save_priv_user= thd->priv_user; - char *save_db= thd->db; + uint save_master_access= thd->master_access; + uint save_db_access= thd->db_access; + uint save_db_length= thd->db_length; + char *save_user= thd->user; + char *save_priv_user= thd->priv_user; + char *save_db= thd->db; USER_CONN *save_user_connect= thd->user_connect; - - if ((uint) ((uchar*) db - net->read_pos) > packet_length) - { // Check if protocol is ok - send_error(net, ER_UNKNOWN_COM_ERROR); + + if (!(thd->user= my_strdup(user, MYF(0)))) + { + thd->user= save_user; + send_error(thd, ER_OUT_OF_RESOURCES); break; } /* Clear variables that are allocated */ - thd->user= 0; thd->user_connect= 0; - if (check_user(thd, COM_CHANGE_USER, user, passwd, db, 0)) - { // Restore old user + int res= check_user(thd, COM_CHANGE_USER, passwd, passwd_len, db, FALSE); + + if (res) + { + /* authentification failure, we shall restore old user */ + if (res > 0) + send_error(thd, ER_UNKNOWN_COM_ERROR); x_free(thd->user); - thd->master_access=save_master_access; - thd->db_access=save_db_access; - thd->db=save_db; - thd->db_length=save_db_length; - thd->user=save_user; - thd->priv_user=save_priv_user; + thd->user= save_user; + thd->priv_user= save_priv_user; thd->user_connect= save_user_connect; - break; + thd->master_access= save_master_access; + thd->db_access= save_db_access; + thd->db= save_db; + thd->db_length= save_db_length; + } + else + { + /* we've authenticated new user */ + if (save_user_connect) + decrease_user_connections(save_user_connect); + x_free((gptr) save_db); + x_free((gptr) save_user); } - if (save_user_connect) - decrease_user_connections(save_user_connect); - x_free((gptr) save_db); - x_free((gptr) save_user); break; } - + case COM_EXECUTE: + { + thd->free_list= NULL; + mysql_stmt_execute(thd, packet, packet_length); + break; + } + case COM_LONG_DATA: + { + mysql_stmt_get_longdata(thd, packet, packet_length); + break; + } + case COM_PREPARE: + { + mysql_stmt_prepare(thd, packet, packet_length); + break; + } + case COM_CLOSE_STMT: + { + mysql_stmt_free(thd, packet); + break; + } + case COM_RESET_STMT: + { + mysql_stmt_reset(thd, packet); + break; + } case COM_QUERY: { - packet_length--; // Remove end null - /* Remove garage at start and end of query */ - while (isspace(packet[0]) && packet_length > 0) - { - packet++; - packet_length--; - } - char *pos=packet+packet_length; // Point at end null - while (packet_length > 0 && (pos[-1] == ';' || isspace(pos[-1]))) - { - pos--; - packet_length--; - } - /* We must allocate some extra memory for query cache */ - thd->query_length= 0; // Extra safety: Avoid races - if (!(thd->query= (char*) thd->memdup_w_gap((gptr) (packet), - packet_length, - thd->db_length+2+ - sizeof(ha_rows)))) - break; - thd->query[packet_length]=0; - thd->packet.shrink(thd->variables.net_buffer_length);// Reclaim some memory - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); + if (alloc_query(thd, packet, packet_length)) + break; // fatal error is set mysql_log.write(thd,command,"%s",thd->query); DBUG_PRINT("query",("%-.4096s",thd->query)); - /* thd->query_length is set by mysql_parse() */ - mysql_parse(thd,thd->query,packet_length); + mysql_parse(thd,thd->query, thd->query_length); + + while (!thd->killed && !thd->is_fatal_error && thd->lex->found_colon) + { + char *packet= thd->lex->found_colon; + /* + Multiple queries exits, execute them individually + in embedded server - just store them to be executed later + */ +#ifndef EMBEDDED_LIBRARY + if (thd->lock || thd->open_tables || thd->derived_tables) + close_thread_tables(thd); +#endif + ulong length= thd->query_length-(ulong)(packet-thd->query); + + /* Remove garbage at start of query */ + while (my_isspace(thd->charset(), *packet) && length > 0) + { + packet++; + length--; + } + VOID(pthread_mutex_lock(&LOCK_thread_count)); + thd->query_length= length; + thd->query= packet; + thd->query_id= query_id++; + VOID(pthread_mutex_unlock(&LOCK_thread_count)); +#ifndef EMBEDDED_LIBRARY + mysql_parse(thd, packet, length); +#else + /* + 'packet' can point inside the query_rest's buffer + so we have to do memmove here + */ + if (thd->query_rest.length() > length) + { + memmove(thd->query_rest.c_ptr(), packet, length); + thd->query_rest.length(length); + } + else + thd->query_rest.copy(length); + break; +#endif /*EMBEDDED_LIBRARY*/ + } + if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),WAIT_PRIOR); DBUG_PRINT("info",("query ready")); @@ -1088,36 +1505,43 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } case COM_FIELD_LIST: // This isn't actually needed #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ break; #else { - char *fields; + char *fields, *pend; TABLE_LIST table_list; + LEX_STRING conv_name; + statistic_increment(com_stat[SQLCOM_SHOW_FIELDS],&LOCK_status); bzero((char*) &table_list,sizeof(table_list)); if (!(table_list.db=thd->db)) { - send_error(net,ER_NO_DB_ERROR); + send_error(thd,ER_NO_DB_ERROR); break; } thd->free_list=0; - table_list.alias= table_list.real_name= thd->strdup(packet); - packet=strend(packet)+1; + pend= strend(packet); + thd->convert_string(&conv_name, system_charset_info, + packet, (uint) (pend-packet), thd->charset()); + table_list.alias= table_list.real_name= conv_name.str; + packet= pend+1; // command not cachable => no gap for data base name if (!(thd->query=fields=thd->memdup(packet,thd->query_length+1))) break; mysql_log.write(thd,command,"%s %s",table_list.real_name,fields); if (lower_case_table_names) - casedn_str(table_list.real_name); + my_casedn_str(files_charset_info, table_list.real_name); remove_escape(table_list.real_name); // This can't have wildcards - if (check_access(thd,SELECT_ACL,table_list.db,&table_list.grant.privilege)) + if (check_access(thd,SELECT_ACL,table_list.db,&table_list.grant.privilege, + 0, 0)) break; - if (grant_option && check_grant(thd,SELECT_ACL,&table_list,2)) + if (grant_option && + check_grant(thd, SELECT_ACL, &table_list, 2, UINT_MAX, 0)) break; mysqld_list_fields(thd,&table_list,fields); - free_items(thd); + free_items(thd->free_list); break; } #endif @@ -1136,10 +1560,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, // null test to handle EOM if (!db || !(alias= thd->strdup(db)) || check_db_name(db)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, db ? db : "NULL"); + net_printf(thd,ER_WRONG_DB_NAME, db ? db : "NULL"); break; } - if (check_access(thd,CREATE_ACL,db,0,1)) + if (check_access(thd,CREATE_ACL,db,0,1,0)) break; mysql_log.write(thd,command,packet); mysql_create_db(thd,(lower_case_table_names == 2 ? alias : db),0,0); @@ -1152,24 +1576,25 @@ bool dispatch_command(enum enum_server_command command, THD *thd, // null test to handle EOM if (!db || !(alias= thd->strdup(db)) || check_db_name(db)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, db ? db : "NULL"); + net_printf(thd,ER_WRONG_DB_NAME, db ? db : "NULL"); break; } - if (check_access(thd,DROP_ACL,db,0,1)) + if (check_access(thd,DROP_ACL,db,0,1,0)) break; if (thd->locked_tables || thd->active_transaction()) { - send_error(&thd->net,ER_LOCK_OR_ACTIVE_TRANSACTION); + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION); break; } mysql_log.write(thd,command,db); mysql_rm_db(thd,alias,0,0); break; } +#ifndef EMBEDDED_LIBRARY case COM_BINLOG_DUMP: { statistic_increment(com_other,&LOCK_status); - slow_command = TRUE; + thd->slow_command = TRUE; if (check_global_access(thd, REPL_SLAVE_ACL)) break; mysql_log.write(thd,command, 0); @@ -1191,6 +1616,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, net->error = 0; break; } +#endif case COM_REFRESH: { statistic_increment(com_stat[SQLCOM_FLUSH],&LOCK_status); @@ -1198,36 +1624,43 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (check_global_access(thd,RELOAD_ACL)) break; mysql_log.write(thd,command,NullS); - /* error sending is deferred to reload_acl_and_cache */ - reload_acl_and_cache(thd, options, (TABLE_LIST*) 0) ; + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, NULL)) + send_error(thd, 0); + else + send_ok(thd); break; } +#ifndef EMBEDDED_LIBRARY case COM_SHUTDOWN: statistic_increment(com_other,&LOCK_status); if (check_global_access(thd,SHUTDOWN_ACL)) break; /* purecov: inspected */ DBUG_PRINT("quit",("Got shutdown command")); mysql_log.write(thd,command,NullS); - send_eof(net); + send_eof(thd); #ifdef __WIN__ sleep(1); // must wait after eof() #endif #ifndef OS2 - send_eof(net); // This is for 'quit request' + send_eof(thd); // This is for 'quit request' #endif - close_connection(net); + close_connection(thd, 0, 1); close_thread_tables(thd); // Free before kill free_root(&thd->mem_root,MYF(0)); free_root(&thd->transaction.mem_root,MYF(0)); kill_mysql(); error=TRUE; break; - +#endif case COM_STATISTICS: { mysql_log.write(thd,command,NullS); statistic_increment(com_stat[SQLCOM_SHOW_STATUS],&LOCK_status); +#ifndef EMBEDDED_LIBRARY char buff[200]; +#else + char *buff= thd->net.last_error; +#endif ulong uptime = (ulong) (thd->start_time - start_time); sprintf((char*) buff, "Uptime: %ld Threads: %d Questions: %lu Slow queries: %ld Opens: %ld Flush tables: %ld Open tables: %u Queries per second avg: %.3f", @@ -1240,22 +1673,25 @@ bool dispatch_command(enum enum_server_command command, THD *thd, sprintf(strend(buff), " Memory in use: %ldK Max memory used: %ldK", (sf_malloc_cur_memory+1023L)/1024L, (sf_malloc_max_memory+1023L)/1024L); - #endif +#endif +#ifndef EMBEDDED_LIBRARY VOID(my_net_write(net, buff,(uint) strlen(buff))); VOID(net_flush(net)); +#endif break; } case COM_PING: statistic_increment(com_other,&LOCK_status); - send_ok(net); // Tell client we are alive + send_ok(thd); // Tell client we are alive break; case COM_PROCESS_INFO: statistic_increment(com_stat[SQLCOM_SHOW_PROCESSLIST],&LOCK_status); if (!thd->priv_user[0] && check_global_access(thd,PROCESS_ACL)) break; mysql_log.write(thd,command,NullS); - mysqld_list_processes(thd,thd->master_access & PROCESS_ACL ? NullS : - thd->priv_user,0); + mysqld_list_processes(thd, + thd->master_access & PROCESS_ACL ? + NullS : thd->priv_user, 0); break; case COM_PROCESS_KILL: { @@ -1264,13 +1700,32 @@ bool dispatch_command(enum enum_server_command command, THD *thd, kill_one_thread(thd,id); break; } + case COM_SET_OPTION: + { + statistic_increment(com_stat[SQLCOM_SET_OPTION], &LOCK_status); + enum_mysql_set_option command= (enum_mysql_set_option) uint2korr(packet); + switch (command) { + case MYSQL_OPTION_MULTI_STATEMENTS_ON: + thd->client_capabilities|= CLIENT_MULTI_STATEMENTS; + send_eof(thd); + break; + case MYSQL_OPTION_MULTI_STATEMENTS_OFF: + thd->client_capabilities&= ~CLIENT_MULTI_STATEMENTS; + send_eof(thd); + break; + default: + send_error(thd, ER_UNKNOWN_COM_ERROR); + break; + } + break; + } case COM_DEBUG: statistic_increment(com_other,&LOCK_status); if (check_global_access(thd, SUPER_ACL)) break; /* purecov: inspected */ mysql_print_status(thd); mysql_log.write(thd,command,NullS); - send_eof(net); + send_eof(thd); break; case COM_SLEEP: case COM_CONNECT: // Impossible here @@ -1278,31 +1733,31 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_DELAYED_INSERT: case COM_END: default: - send_error(net, ER_UNKNOWN_COM_ERROR); + send_error(thd, ER_UNKNOWN_COM_ERROR); break; } - if (thd->lock || thd->open_tables) + if (thd->lock || thd->open_tables || thd->derived_tables) { thd->proc_info="closing tables"; close_thread_tables(thd); /* Free tables */ } - if (thd->fatal_error) - send_error(net,0); // End of memory ? + if (thd->is_fatal_error) + send_error(thd,0); // End of memory ? time_t start_of_query=thd->start_time; thd->end_time(); // Set start time /* If not reading from backup and if the query took too long */ - if (!slow_command && !thd->user_time) // do not log 'slow_command' queries + if (!thd->slow_command && !thd->user_time) // do not log 'slow_command' queries { thd->proc_info="logging slow query"; if ((ulong) (thd->start_time - thd->time_after_lock) > thd->variables.long_query_time || - ((thd->lex.select_lex.options & - (QUERY_NO_INDEX_USED | QUERY_NO_GOOD_INDEX_USED)) && - (specialflag & SPECIAL_LONG_LOG_FORMAT))) + ((thd->server_status & + (SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED)) && + (specialflag & SPECIAL_LOG_QUERIES_NOT_USING_INDEXES))) { long_query_count++; mysql_slow_log.write(thd, thd->query, thd->query_length, start_of_query); @@ -1321,24 +1776,81 @@ bool dispatch_command(enum enum_server_command command, THD *thd, DBUG_RETURN(error); } + +/* + Read query from packet and store in thd->query + Used in COM_QUERY and COM_PREPARE + + DESCRIPTION + Sets the following THD variables: + query + query_length + + RETURN VALUES + 0 ok + 1 error; In this case thd->fatal_error is set +*/ + +bool alloc_query(THD *thd, char *packet, ulong packet_length) +{ + packet_length--; // Remove end null + /* Remove garbage at start and end of query */ + while (my_isspace(thd->charset(),packet[0]) && packet_length > 0) + { + packet++; + packet_length--; + } + char *pos=packet+packet_length; // Point at end null + while (packet_length > 0 && + (pos[-1] == ';' || my_isspace(thd->charset() ,pos[-1]))) + { + pos--; + packet_length--; + } + /* We must allocate some extra memory for query cache */ + thd->query_length= 0; // Extra safety: Avoid races + if (!(thd->query= (char*) thd->memdup_w_gap((gptr) (packet), + packet_length, + thd->db_length+ 1 + + QUERY_CACHE_FLAGS_SIZE))) + return 1; + thd->query[packet_length]=0; + thd->query_length= packet_length; + thd->packet.shrink(thd->variables.net_buffer_length);// Reclaim some memory + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); + return 0; +} + /**************************************************************************** ** mysql_execute_command ** Execute command saved in thd and current_lex->sql_command ****************************************************************************/ void -mysql_execute_command(void) +mysql_execute_command(THD *thd) { - int res=0; - THD *thd=current_thd; - LEX *lex= &thd->lex; - TABLE_LIST *tables=(TABLE_LIST*) lex->select_lex.table_list.first; - SELECT_LEX *select_lex = lex->select; + int res= 0; + LEX *lex= thd->lex; + TABLE_LIST *tables= (TABLE_LIST*) lex->select_lex.table_list.first; + SELECT_LEX *select_lex= &lex->select_lex; + SELECT_LEX_UNIT *unit= &lex->unit; DBUG_ENTER("mysql_execute_command"); + /* + Reset warning count for each query that uses tables + A better approach would be to reset this for any commands + that is not a SHOW command or a select that only access local + variables, but for now this is probably good enough. + */ + if (tables || &lex->select_lex != lex->all_selects_list) + mysql_reset_errors(thd); + +#ifdef HAVE_REPLICATION if (thd->slave_thread) { - /* + /* Skip if we are in the slave thread, some table rules have been given and the table list says the query should not be replicated */ @@ -1350,9 +1862,9 @@ mysql_execute_command(void) } #ifndef TO_BE_DELETED /* - This is a workaround to deal with the shortcoming in 3.23.44-3.23.46 - masters in RELEASE_LOCK() logging. We re-write SELECT RELEASE_LOCK() - as DO RELEASE_LOCK() + This is a workaround to deal with the shortcoming in 3.23.44-3.23.46 + masters in RELEASE_LOCK() logging. We re-write SELECT RELEASE_LOCK() + as DO RELEASE_LOCK() */ if (lex->sql_command == SQLCOM_SELECT) { @@ -1361,12 +1873,11 @@ mysql_execute_command(void) } #endif } - - if (lex->select_lex.next && - create_total_list(thd,lex,&tables, - (lex->sql_command == SQLCOM_CREATE_TABLE))) +#endif /* !HAVE_REPLICATION */ + if (&lex->select_lex != lex->all_selects_list && + lex->unit.create_total_list(thd, lex, &tables)) DBUG_VOID_RETURN; - + /* When option readonly is set deny operations which change tables. Except for the replication thread and the 'super' users. @@ -1375,7 +1886,7 @@ mysql_execute_command(void) !(thd->slave_thread || (thd->master_access & SUPER_ACL)) && (uc_update_queries[lex->sql_command] > 0)) { - send_error(&thd->net,ER_CANT_UPDATE_WITH_READLOCK); + net_printf(thd, ER_OPTION_PREVENTS_STATEMENT, "--read-only"); DBUG_VOID_RETURN; } @@ -1383,111 +1894,152 @@ mysql_execute_command(void) switch (lex->sql_command) { case SQLCOM_SELECT: { - select_result *result; - if (select_lex->options & SELECT_DESCRIBE) - lex->exchange=0; + /* assign global limit variable if limit is not given */ + { + SELECT_LEX *param= lex->unit.global_parameters; + if (!param->explicit_limit) + param->select_limit= thd->variables.select_limit; + } + + select_result *result=lex->result; if (tables) { res=check_table_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL, - tables); + tables,0); } else res=check_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL, - any_db); + any_db,0,0,0); if (res) { res=0; break; // Error message is given } - - thd->offset_limit=select_lex->offset_limit; - thd->select_limit=select_lex->select_limit+select_lex->offset_limit; - if (thd->select_limit < select_lex->select_limit) - thd->select_limit= HA_POS_ERROR; // no limit - if (thd->select_limit == HA_POS_ERROR) + /* + In case of single SELECT unit->global_parameters points on first SELECT + TODO: move counters to SELECT_LEX + */ + unit->offset_limit_cnt= (ha_rows) unit->global_parameters->offset_limit; + unit->select_limit_cnt= (ha_rows) (unit->global_parameters->select_limit+ + unit->global_parameters->offset_limit); + if (unit->select_limit_cnt < + (ha_rows) unit->global_parameters->select_limit) + unit->select_limit_cnt= HA_POS_ERROR; // no limit + if (unit->select_limit_cnt == HA_POS_ERROR && !select_lex->next_select()) select_lex->options&= ~OPTION_FOUND_ROWS; - if (lex->exchange) + if (!(res=open_and_lock_tables(thd,tables))) { - if (lex->exchange->dumpfile) + if (lex->describe) { - if (!(result=new select_dump(lex->exchange))) + if (!(result= new select_send())) { - res= -1; - break; + send_error(thd, ER_OUT_OF_RESOURCES); + DBUG_VOID_RETURN; } + else + thd->send_explain_fields(result); + res= mysql_explain_union(thd, &thd->lex->unit, result); + MYSQL_LOCK *save_lock= thd->lock; + thd->lock= (MYSQL_LOCK *)0; + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + thd->lex->unit.print(&str); + str.append('\0'); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_YES, str.ptr()); + } + result->send_eof(); + thd->lock= save_lock; } else { - if (!(result=new select_export(lex->exchange))) + if (!result) { - res= -1; - break; + if (!(result=new select_send())) + { + res= -1; + break; + } } + query_cache_store_query(thd, tables); + res=handle_select(thd, lex, result); } } - else if (!(result=new select_send())) - { - res= -1; -#ifdef DELETE_ITEMS - delete select_lex->having; - delete select_lex->where; -#endif - break; - } - else - { - /* - Normal select: - Change lock if we are using SELECT HIGH PRIORITY, - FOR UPDATE or IN SHARE MODE - - TODO: Delete the following loop when locks is set by sql_yacc - */ - TABLE_LIST *table; - for (table = tables ; table ; table=table->next) - table->lock_type= lex->lock_option; - } - - if (!(res=open_and_lock_tables(thd,tables))) - { - query_cache_store_query(thd, tables); - res=handle_select(thd, lex, result); - } - else - delete result; break; } + case SQLCOM_DO: - res=mysql_do(thd, *lex->insert_list); + if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || + (res= open_and_lock_tables(thd,tables)))) + break; + + res= mysql_do(thd, *lex->insert_list); + if (thd->net.report_error) + res= -1; break; case SQLCOM_EMPTY_QUERY: - send_ok(&thd->net); + send_ok(thd); + break; + + case SQLCOM_HELP: + res= mysqld_help(thd,lex->help_arg); break; +#ifndef EMBEDDED_LIBRARY case SQLCOM_PURGE: { if (check_global_access(thd, SUPER_ACL)) goto error; + // PURGE MASTER LOGS TO 'file' res = purge_master_logs(thd, lex->to_log); break; } + case SQLCOM_PURGE_BEFORE: + { + if (check_global_access(thd, SUPER_ACL)) + goto error; + // PURGE MASTER LOGS BEFORE 'data' + res = purge_master_logs_before_date(thd, lex->purge_time); + break; + } +#endif + case SQLCOM_SHOW_WARNS: + { + res= mysqld_show_warnings(thd, (ulong) + ((1L << (uint) MYSQL_ERROR::WARN_LEVEL_NOTE) | + (1L << (uint) MYSQL_ERROR::WARN_LEVEL_WARN) | + (1L << (uint) MYSQL_ERROR::WARN_LEVEL_ERROR) + )); + break; + } + case SQLCOM_SHOW_ERRORS: + { + res= mysqld_show_warnings(thd, (ulong) + (1L << (uint) MYSQL_ERROR::WARN_LEVEL_ERROR)); + break; + } case SQLCOM_SHOW_NEW_MASTER: { if (check_global_access(thd, REPL_SLAVE_ACL)) goto error; /* This query don't work now. See comment in repl_failsafe.cc */ #ifndef WORKING_NEW_MASTER - net_printf(&thd->net, ER_NOT_SUPPORTED_YET, "SHOW NEW MASTER"); + net_printf(thd, ER_NOT_SUPPORTED_YET, "SHOW NEW MASTER"); res= 1; #else res = show_new_master(thd); #endif break; } + +#ifdef HAVE_REPLICATION case SQLCOM_SHOW_SLAVE_HOSTS: { if (check_global_access(thd, REPL_SLAVE_ACL)) @@ -1502,12 +2054,15 @@ mysql_execute_command(void) res = show_binlog_events(thd); break; } +#endif + case SQLCOM_BACKUP_TABLE: { if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL, tables) || + check_table_access(thd,SELECT_ACL, tables,0) || check_global_access(thd, FILE_ACL)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; res = mysql_backup_table(thd, tables); break; @@ -1515,13 +2070,31 @@ mysql_execute_command(void) case SQLCOM_RESTORE_TABLE: { if (check_db_used(thd,tables) || - check_table_access(thd, INSERT_ACL, tables) || + check_table_access(thd, INSERT_ACL, tables,0) || check_global_access(thd, FILE_ACL)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; res = mysql_restore_table(thd, tables); break; } - + case SQLCOM_ASSIGN_TO_KEYCACHE: + { + if (check_db_used(thd, tables) || + check_access(thd, INDEX_ACL, tables->db, + &tables->grant.privilege, 0, 0)) + goto error; + res= mysql_assign_to_keycache(thd, tables, &lex->name_and_length); + break; + } + case SQLCOM_PRELOAD_KEYS: + { + if (check_db_used(thd, tables) || + check_access(thd, INDEX_ACL, tables->db, + &tables->grant.privilege, 0, 0)) + goto error; + res = mysql_preload_keys(thd, tables); + break; + } #ifdef HAVE_REPLICATION case SQLCOM_CHANGE_MASTER: { @@ -1550,7 +2123,7 @@ mysql_execute_command(void) res = show_binlog_info(thd); break; } - + case SQLCOM_LOAD_MASTER_DATA: // sync with master if (check_global_access(thd, SUPER_ACL)) goto error; @@ -1559,9 +2132,7 @@ mysql_execute_command(void) else res = load_master_data(thd); break; - #endif /* HAVE_REPLICATION */ - #ifdef HAVE_INNOBASE_DB case SQLCOM_SHOW_INNODB_STATUS: { @@ -1571,24 +2142,24 @@ mysql_execute_command(void) break; } #endif - #ifdef HAVE_REPLICATION case SQLCOM_LOAD_MASTER_TABLE: { if (!tables->db) tables->db=thd->db; - if (check_access(thd,CREATE_ACL,tables->db,&tables->grant.privilege)) + if (check_access(thd,CREATE_ACL,tables->db,&tables->grant.privilege,0,0)) goto error; /* purecov: inspected */ if (grant_option) { /* Check that the first table has CREATE privilege */ - TABLE_LIST *tmp_table_list=tables->next; - tables->next=0; - bool error=check_grant(thd,CREATE_ACL,tables); - tables->next=tmp_table_list; - if (error) + if (check_grant(thd, CREATE_ACL, tables, 0, 1, 0)) goto error; } + if (strlen(tables->real_name) > NAME_LEN) + { + net_printf(thd,ER_WRONG_TABLE_NAME, tables->real_name); + break; + } pthread_mutex_lock(&LOCK_active_mi); /* fetch_master_table will send the error to the client on failure. @@ -1597,7 +2168,7 @@ mysql_execute_command(void) if (!fetch_master_table(thd, tables->db, tables->real_name, active_mi, 0, 0)) { - send_ok(&thd->net); + send_ok(thd); } pthread_mutex_unlock(&LOCK_active_mi); break; @@ -1606,101 +2177,108 @@ mysql_execute_command(void) case SQLCOM_CREATE_TABLE: { - ulong want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? - CREATE_TMP_ACL : CREATE_ACL); - if (!tables->db) - tables->db=thd->db; - lex->create_info.alias= tables->alias; - if (check_access(thd,want_priv,tables->db,&tables->grant.privilege) || - check_merge_table_access(thd, tables->db, - (TABLE_LIST *) - lex->create_info.merge_list.first)) - goto error; /* purecov: inspected */ - if (grant_option && want_priv != CREATE_TMP_ACL) - { - /* Check that the first table has CREATE privilege */ - TABLE_LIST *tmp_table_list=tables->next; - tables->next=0; - bool error=check_grant(thd, want_priv, tables); - tables->next=tmp_table_list; - if (error) - goto error; - } - if (strlen(tables->real_name) > NAME_LEN) - { - net_printf(&thd->net, ER_WRONG_TABLE_NAME, tables->alias); - res=0; - break; - } + /* Skip first table, which is the table we are creating */ + TABLE_LIST *create_table, *create_table_local; + tables= lex->unlink_first_table(tables, &create_table, + &create_table_local); + + if ((res= create_table_precheck(thd, tables, create_table))) + goto unsent_create_error; + #ifndef HAVE_READLINK lex->create_info.data_file_name=lex->create_info.index_file_name=0; #else /* Fix names if symlinked tables */ if (append_file_to_dir(thd, &lex->create_info.data_file_name, - tables->real_name) || + create_table->real_name) || append_file_to_dir(thd,&lex->create_info.index_file_name, - tables->real_name)) + create_table->real_name)) { res=-1; - break; + goto unsent_create_error; } #endif + /* + If we are using SET CHARSET without DEFAULT, add an implicite + DEFAULT to not confuse old users. (This may change). + */ + if ((lex->create_info.used_fields & + (HA_CREATE_USED_DEFAULT_CHARSET | HA_CREATE_USED_CHARSET)) == + HA_CREATE_USED_CHARSET) + { + lex->create_info.used_fields&= ~HA_CREATE_USED_CHARSET; + lex->create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; + lex->create_info.default_table_charset= lex->create_info.table_charset; + lex->create_info.table_charset= 0; + } if (select_lex->item_list.elements) // With select { select_result *result; if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && - check_dup(tables->db, tables->real_name, tables->next)) - { - net_printf(&thd->net,ER_INSERT_TABLE_USED,tables->real_name); - DBUG_VOID_RETURN; - } - if (tables->next) + find_real_table_in_list(tables, create_table->db, + create_table->real_name)) { - TABLE_LIST *table; - if (check_table_access(thd, SELECT_ACL, tables->next)) - goto error; // Error message is given - /* TODO: Delete the following loop when locks is set by sql_yacc */ - for (table = tables->next ; table ; table=table->next) - table->lock_type= lex->lock_option; + net_printf(thd,ER_UPDATE_TABLE_USED, create_table->real_name); + goto create_error; } + if (tables && check_table_access(thd, SELECT_ACL, tables,0)) + goto create_error; // Error message is given select_lex->options|= SELECT_NO_UNLOCK; - thd->offset_limit=select_lex->offset_limit; - thd->select_limit=select_lex->select_limit+select_lex->offset_limit; - if (thd->select_limit < select_lex->select_limit) - thd->select_limit= HA_POS_ERROR; // No limit - - /* Skip first table, which is the table we are creating */ - lex->select_lex.table_list.first= - (byte*) (((TABLE_LIST *) lex->select_lex.table_list.first)->next); - if (!(res=open_and_lock_tables(thd,tables->next))) + unit->offset_limit_cnt= select_lex->offset_limit; + unit->select_limit_cnt= select_lex->select_limit+ + select_lex->offset_limit; + if (unit->select_limit_cnt < select_lex->select_limit) + unit->select_limit_cnt= HA_POS_ERROR; // No limit + + if (!(res=open_and_lock_tables(thd,tables))) { - if ((result=new select_create(tables->db ? tables->db : thd->db, - tables->real_name, + res= -1; // If error + if ((result=new select_create(create_table->db, + create_table->real_name, &lex->create_info, lex->create_list, lex->key_list, select_lex->item_list,lex->duplicates))) res=handle_select(thd, lex, result); - else - res= -1; + //reset for PS + lex->create_list.empty(); + lex->key_list.empty(); } } else // regular create { - res = mysql_create_table(thd,tables->db ? tables->db : thd->db, - tables->real_name, - &lex->create_info, - lex->create_list, - lex->key_list,0, 0); // do logging + if (lex->name) + res= mysql_create_like_table(thd, create_table, &lex->create_info, + (Table_ident *)lex->name); + else + { + res= mysql_create_table(thd,create_table->db, + create_table->real_name, &lex->create_info, + lex->create_list, + lex->key_list,0,0,0); // do logging + } if (!res) - send_ok(&thd->net); + send_ok(thd); } + + // put tables back for PS rexecuting + tables= lex->link_first_table_back(tables, create_table, + create_table_local); + break; + +create_error: + res= 1; //error reported +unsent_create_error: + // put tables back for PS rexecuting + tables= lex->link_first_table_back(tables, create_table, + create_table_local); break; } case SQLCOM_CREATE_INDEX: - if (check_one_table_access(thd, INDEX_ACL, tables, 0)) + if (check_one_table_access(thd, INDEX_ACL, tables)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; if (end_active_trans(thd)) res= -1; else @@ -1731,7 +2309,7 @@ mysql_execute_command(void) */ if (thd->locked_tables || thd->active_transaction()) { - send_error(&thd->net,ER_LOCK_OR_ACTIVE_TRANSACTION); + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION); break; } { @@ -1744,28 +2322,28 @@ mysql_execute_command(void) case SQLCOM_ALTER_TABLE: #if defined(DONT_ALLOW_SHOW_COMMANDS) - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ break; #else { ulong priv=0; if (lex->name && (!lex->name[0] || strlen(lex->name) > NAME_LEN)) { - net_printf(&thd->net,ER_WRONG_TABLE_NAME,lex->name); + net_printf(thd, ER_WRONG_TABLE_NAME, lex->name); res=0; break; } if (!select_lex->db) select_lex->db=tables->db; - if (check_access(thd,ALTER_ACL,tables->db,&tables->grant.privilege) || - check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv) || - check_merge_table_access(thd, tables->db, + if (check_access(thd,ALTER_ACL,tables->db,&tables->grant.privilege,0,0) || + check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv,0,0)|| + check_merge_table_access(thd, tables->db, (TABLE_LIST *) lex->create_info.merge_list.first)) goto error; /* purecov: inspected */ if (grant_option) { - if (check_grant(thd,ALTER_ACL,tables)) + if (check_grant(thd, ALTER_ACL, tables, 0, UINT_MAX, 0)) goto error; if (lex->name && !test_all_bits(priv,INSERT_ACL | CREATE_ACL)) { // Rename of table @@ -1774,7 +2352,8 @@ mysql_execute_command(void) tmp_table.real_name=lex->name; tmp_table.db=select_lex->db; tmp_table.grant.privilege=priv; - if (check_grant(thd,INSERT_ACL | CREATE_ACL, &tmp_table)) + if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, 0, + UINT_MAX, 0)) goto error; } } @@ -1785,17 +2364,22 @@ mysql_execute_command(void) res= -1; else { + thd->slow_command=TRUE; res= mysql_alter_table(thd, select_lex->db, lex->name, &lex->create_info, tables, lex->create_list, lex->key_list, lex->drop_list, lex->alter_list, + select_lex->order_list.elements, (ORDER *) select_lex->order_list.first, - lex->drop_primary, lex->duplicates, - lex->alter_keys_onoff, lex->simple_alter); + lex->alter_flags, + lex->duplicates, + lex->alter_keys_onoff, + lex->tablespace_op, + lex->simple_alter); } break; } -#endif +#endif /*DONT_ALLOW_SHOW_COMMANDS*/ case SQLCOM_RENAME_TABLE: { TABLE_LIST *table; @@ -1804,20 +2388,25 @@ mysql_execute_command(void) for (table=tables ; table ; table=table->next->next) { if (check_access(thd, ALTER_ACL | DROP_ACL, table->db, - &table->grant.privilege) || + &table->grant.privilege,0,0) || check_access(thd, INSERT_ACL | CREATE_ACL, table->next->db, - &table->next->grant.privilege)) + &table->next->grant.privilege,0,0)) goto error; if (grant_option) { TABLE_LIST old_list,new_list; + /* + we do not need initialize old_list and new_list because we will + come table[0] and table->next[0] there + */ old_list=table[0]; new_list=table->next[0]; old_list.next=new_list.next=0; - if (check_grant(thd,ALTER_ACL,&old_list) || + if (check_grant(thd, ALTER_ACL, &old_list, 0, UINT_MAX, 0) || (!test_all_bits(table->next->grant.privilege, INSERT_ACL | CREATE_ACL) && - check_grant(thd,INSERT_ACL | CREATE_ACL, &new_list))) + check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, 0, + UINT_MAX, 0))) goto error; } } @@ -1828,9 +2417,10 @@ mysql_execute_command(void) res= -1; break; } +#ifndef EMBEDDED_LIBRARY case SQLCOM_SHOW_BINLOGS: #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else { @@ -1839,43 +2429,75 @@ mysql_execute_command(void) res = show_binlogs(thd); break; } -#endif +#endif +#endif /* EMBEDDED_LIBRARY */ case SQLCOM_SHOW_CREATE: #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else { if (check_db_used(thd, tables) || check_access(thd, SELECT_ACL | EXTRA_ACL, tables->db, - &tables->grant.privilege)) + &tables->grant.privilege,0,0)) goto error; res = mysqld_show_create(thd, tables); break; } #endif + case SQLCOM_CHECKSUM: + { + if (check_db_used(thd,tables) || + check_table_access(thd, SELECT_ACL | EXTRA_ACL , tables,0)) + goto error; /* purecov: inspected */ + res = mysql_checksum_table(thd, tables, &lex->check_opt); + break; + } case SQLCOM_REPAIR: { if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables)) + check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; res = mysql_repair_table(thd, tables, &lex->check_opt); + /* ! we write after unlocking the table */ + if (!res && !lex->no_write_to_binlog) + { + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); + } + } break; } case SQLCOM_CHECK: { if (check_db_used(thd,tables) || - check_table_access(thd, SELECT_ACL | EXTRA_ACL , tables)) + check_table_access(thd, SELECT_ACL | EXTRA_ACL , tables,0)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; res = mysql_check_table(thd, tables, &lex->check_opt); break; } case SQLCOM_ANALYZE: { if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables)) + check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; res = mysql_analyze_table(thd, tables, &lex->check_opt); + /* ! we write after unlocking the table */ + if (!res && !lex->no_write_to_binlog) + { + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); + } + } break; } @@ -1883,8 +2505,9 @@ mysql_execute_command(void) { HA_CREATE_INFO create_info; if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables)) + check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) goto error; /* purecov: inspected */ + thd->slow_command=TRUE; if (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) { /* Use ALTER TABLE */ @@ -1896,106 +2519,73 @@ mysql_execute_command(void) bzero((char*) &create_info,sizeof(create_info)); create_info.db_type=DB_TYPE_DEFAULT; create_info.row_type=ROW_TYPE_DEFAULT; + create_info.default_table_charset=default_charset_info; res= mysql_alter_table(thd, NullS, NullS, &create_info, tables, lex->create_list, lex->key_list, lex->drop_list, lex->alter_list, - (ORDER *) 0, - 0,DUP_ERROR); + 0, (ORDER *) 0, 0, + DUP_ERROR); } else res = mysql_optimize_table(thd, tables, &lex->check_opt); - break; - } - case SQLCOM_UPDATE: - if (check_db_used(thd,tables)) - goto error; - if (select_lex->item_list.elements != lex->value_list.elements) - { - send_error(&thd->net,ER_WRONG_VALUE_COUNT); - DBUG_VOID_RETURN; - } - if (select_lex->table_list.elements == 1) - { - if (check_one_table_access(thd, UPDATE_ACL, tables, 0)) - goto error; /* purecov: inspected */ - - - res= mysql_update(thd,tables, - select_lex->item_list, - lex->value_list, - select_lex->where, - (ORDER *) select_lex->order_list.first, - select_lex->select_limit, - lex->duplicates); - } - else + /* ! we write after unlocking the table */ + if (!res && !lex->no_write_to_binlog) { - const char *msg= 0; - TABLE_LIST *table; - lex->sql_command= SQLCOM_MULTI_UPDATE; - - /* - Ensure that we have UPDATE or SELECT privilege for each table - The exact privilege is checked in mysql_multi_update() - */ - for (table= tables ; table ; table= table->next) - { - TABLE_LIST *save= table->next; - table->next= 0; - if (check_one_table_access(thd, UPDATE_ACL, table, 1) && - check_one_table_access(thd, SELECT_ACL, table, 0)) - goto error; - table->next= save; - } - if (select_lex->order_list.elements) - msg="ORDER BY"; - else if (select_lex->select_limit && select_lex->select_limit != - HA_POS_ERROR) - msg="LIMIT"; - if (msg) + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) { - net_printf(&thd->net, ER_WRONG_USAGE, "UPDATE", msg); - res= 1; - break; + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); } - res= mysql_multi_update(thd,tables, - &select_lex->item_list, - &lex->value_list, - select_lex->where, - select_lex->options, - lex->duplicates); } break; - case SQLCOM_INSERT: - if (check_one_table_access(thd, INSERT_ACL, tables, 0)) - goto error; /* purecov: inspected */ - res = mysql_insert(thd,tables,lex->field_list,lex->many_values, - lex->duplicates); + } + case SQLCOM_UPDATE: + if (update_precheck(thd, tables)) + break; + res= mysql_update(thd,tables, + select_lex->item_list, + lex->value_list, + select_lex->where, + select_lex->order_list.elements, + (ORDER *) select_lex->order_list.first, + select_lex->select_limit, + lex->duplicates); + if (thd->net.report_error) + res= -1; break; + case SQLCOM_UPDATE_MULTI: + { + if ((res= multi_update_precheck(thd, tables))) + break; + res= mysql_multi_update(thd,tables, + &select_lex->item_list, + &lex->value_list, + select_lex->where, + select_lex->options, + lex->duplicates, unit, select_lex); + break; + } case SQLCOM_REPLACE: - if (check_one_table_access(thd, INSERT_ACL | DELETE_ACL, tables, 0)) - goto error; /* purecov: inspected */ + case SQLCOM_INSERT: + { + my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); + if ((res= insert_precheck(thd, tables, update))) + break; res = mysql_insert(thd,tables,lex->field_list,lex->many_values, - DUP_REPLACE); + select_lex->item_list, lex->value_list, + (update ? DUP_UPDATE : lex->duplicates)); + if (thd->net.report_error) + res= -1; break; + } case SQLCOM_REPLACE_SELECT: case SQLCOM_INSERT_SELECT: { - { - /* - Check that we have modify privileges for the first table and - select privileges for the rest - */ - ulong privilege= (lex->sql_command == SQLCOM_INSERT_SELECT ? - INSERT_ACL : INSERT_ACL | DELETE_ACL); - TABLE_LIST *save_next=tables->next; - tables->next=0; - if (check_one_table_access(thd, privilege, tables, 0)) - goto error; - tables->next=save_next; - if ((res=check_table_access(thd, SELECT_ACL, save_next))) - goto error; - } + TABLE_LIST *first_local_table= (TABLE_LIST *) select_lex->table_list.first; + if ((res= insert_select_precheck(thd, tables))) + break; + /* Fix lock for first table */ if (tables->lock_type == TL_WRITE_DELAYED) tables->lock_type= TL_WRITE; @@ -2004,121 +2594,125 @@ mysql_execute_command(void) select_lex->options|= SELECT_NO_UNLOCK; select_result *result; - thd->offset_limit=select_lex->offset_limit; - thd->select_limit=select_lex->select_limit+select_lex->offset_limit; - if (thd->select_limit < select_lex->select_limit) - thd->select_limit= HA_POS_ERROR; // No limit + unit->offset_limit_cnt= select_lex->offset_limit; + unit->select_limit_cnt= select_lex->select_limit+select_lex->offset_limit; + if (unit->select_limit_cnt < select_lex->select_limit) + unit->select_limit_cnt= HA_POS_ERROR; // No limit - if (check_dup(tables->db, tables->real_name, tables->next)) + if (find_real_table_in_list(tables->next, tables->db, tables->real_name)) { /* Using same table for INSERT and SELECT */ select_lex->options |= OPTION_BUFFER_RESULT; } - { - /* TODO: Delete the following loop when locks is set by sql_yacc */ - TABLE_LIST *table; - for (table = tables->next ; table ; table=table->next) - table->lock_type= lex->lock_option; - } - /* Skip first table, which is the table we are inserting in */ - lex->select_lex.table_list.first= - (byte*) (((TABLE_LIST *) lex->select_lex.table_list.first)->next); + if (!(res=open_and_lock_tables(thd, tables))) { if ((result=new select_insert(tables->table,&lex->field_list, lex->duplicates))) + /* Skip first table, which is the table we are inserting in */ + lex->select_lex.table_list.first= (byte*) first_local_table->next; + lex->select_lex.resolve_mode= SELECT_LEX::NOMATTER_MODE; res=handle_select(thd,lex,result); + /* revert changes for SP */ + lex->select_lex.table_list.first= (byte*) first_local_table; + lex->select_lex.resolve_mode= SELECT_LEX::INSERT_MODE; + if (thd->net.report_error) + res= -1; } else res= -1; break; } case SQLCOM_TRUNCATE: - if (check_one_table_access(thd, DELETE_ACL, tables, 0)) - goto error; /* purecov: inspected */ + if (check_one_table_access(thd, DELETE_ACL, tables)) + goto error; /* Don't allow this within a transaction because we want to use re-generate table */ if (thd->locked_tables || thd->active_transaction()) { - send_error(&thd->net,ER_LOCK_OR_ACTIVE_TRANSACTION,NullS); + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION,NullS); goto error; } res=mysql_truncate(thd,tables); break; case SQLCOM_DELETE: { - if (check_one_table_access(thd, DELETE_ACL, tables, 0)) - goto error; - // Set privilege for the WHERE clause - tables->grant.want_privilege=(SELECT_ACL & ~tables->grant.privilege); + if ((res= delete_precheck(thd, tables))) + break; res = mysql_delete(thd,tables, select_lex->where, - (ORDER*) select_lex->order_list.first, + &select_lex->order_list, select_lex->select_limit, select_lex->options); + if (thd->net.report_error) + res= -1; break; } case SQLCOM_DELETE_MULTI: { - TABLE_LIST *aux_tables=(TABLE_LIST *)thd->lex.auxilliary_table_list.first; - TABLE_LIST *auxi; - uint table_count=0; + TABLE_LIST *aux_tables= + (TABLE_LIST *)thd->lex->auxilliary_table_list.first; + TABLE_LIST *target_tbl; + uint table_count; multi_delete *result; - /* sql_yacc guarantees that tables and aux_tables are not zero */ - if (check_db_used(thd, tables) || check_db_used(thd,aux_tables) || - check_table_access(thd,SELECT_ACL, tables) || - check_table_access(thd,DELETE_ACL, aux_tables)) - goto error; - if ((thd->options & OPTION_SAFE_UPDATES) && !select_lex->where) - { - send_error(&thd->net,ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE); - goto error; - } - for (auxi=(TABLE_LIST*) aux_tables ; auxi ; auxi=auxi->next) - { - table_count++; - /* All tables in aux_tables must be found in FROM PART */ - TABLE_LIST *walk; - for (walk=(TABLE_LIST*) tables ; walk ; walk=walk->next) - { - if (!strcmp(auxi->real_name,walk->real_name) && - !strcmp(walk->db,auxi->db)) - break; - } - if (!walk) - { - net_printf(&thd->net,ER_NONUNIQ_TABLE,auxi->real_name); - goto error; - } - walk->lock_type= auxi->lock_type; - // Store address to table as we need it later - auxi->table= my_reinterpret_cast(TABLE *) (walk); - } - if (add_item_to_list(new Item_null())) + if ((res= multi_delete_precheck(thd, tables, &table_count))) + break; + + /* condition will be TRUE on SP re-excuting */ + if (select_lex->item_list.elements != 0) + select_lex->item_list.empty(); + if (add_item_to_list(thd, new Item_null())) { res= -1; break; } - tables->grant.want_privilege=(SELECT_ACL & ~tables->grant.privilege); + thd->proc_info="init"; if ((res=open_and_lock_tables(thd,tables))) break; /* Fix tables-to-be-deleted-from list to point at opened tables */ - for (auxi=(TABLE_LIST*) aux_tables ; auxi ; auxi=auxi->next) - auxi->table= (my_reinterpret_cast(TABLE_LIST*) (auxi->table))->table; - - if (!thd->fatal_error && (result= new multi_delete(thd,aux_tables, - table_count))) - { - res=mysql_select(thd,tables,select_lex->item_list, - select_lex->where, - (ORDER *)NULL,(ORDER *)NULL,(Item *)NULL, - (ORDER *)NULL, - select_lex->options | thd->options | - SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK, - result); + for (target_tbl= (TABLE_LIST*) aux_tables; + target_tbl; + target_tbl= target_tbl->next) + { + target_tbl->table= target_tbl->table_list->table; + /* + Multi-delete can't be constructed over-union => we always have + single SELECT on top and have to check underlaying SELECTs of it + */ + for (SELECT_LEX_UNIT *un= lex->select_lex.first_inner_unit(); + un; + un= un->next_unit()) + { + if (un->first_select()->linkage != DERIVED_TABLE_TYPE && + un->check_updateable(target_tbl->table_list->db, + target_tbl->table_list->real_name)) + { + my_error(ER_UPDATE_TABLE_USED, MYF(0), + target_tbl->table_list->real_name); + res= -1; + break; + } + } + } + + if (!thd->is_fatal_error && (result= new multi_delete(thd,aux_tables, + table_count))) + { + res= mysql_select(thd, &select_lex->ref_pointer_array, + select_lex->get_table_list(), + select_lex->with_wild, + select_lex->item_list, + select_lex->where, + 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, + (ORDER *)NULL, + select_lex->options | thd->options | + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK, + result, unit, select_lex); + if (thd->net.report_error) + res= -1; delete result; } else @@ -2128,27 +2722,35 @@ mysql_execute_command(void) } case SQLCOM_DROP_TABLE: { - if (check_table_access(thd,DROP_ACL,tables)) - goto error; /* purecov: inspected */ - /* - If this is a slave thread, we may sometimes execute some - DROP / * 40005 TEMPORARY * / TABLE - that come from parts of binlogs (likely if we use RESET SLAVE or CHANGE - MASTER TO), while the temporary table has already been dropped. - To not generate such irrelevant "table does not exist errors", - we silently add IF EXISTS if TEMPORARY was used. - */ - if (thd->slave_thread && lex->drop_temporary) - lex->drop_if_exists= 1; - if (end_active_trans(thd)) - res= -1; + if (!lex->drop_temporary) + { + if (check_table_access(thd,DROP_ACL,tables,0)) + goto error; /* purecov: inspected */ + if (end_active_trans(thd)) + { + res= -1; + break; + } + } else - res = mysql_rm_table(thd,tables,lex->drop_if_exists); + { + /* + If this is a slave thread, we may sometimes execute some + DROP / * 40005 TEMPORARY * / TABLE + that come from parts of binlogs (likely if we use RESET SLAVE or CHANGE + MASTER TO), while the temporary table has already been dropped. + To not generate such irrelevant "table does not exist errors", + we silently add IF EXISTS if TEMPORARY was used. + */ + if (thd->slave_thread) + lex->drop_if_exists= 1; + } + res= mysql_rm_table(thd,tables,lex->drop_if_exists, lex->drop_temporary); } break; case SQLCOM_DROP_INDEX: - if (check_one_table_access(thd, INDEX_ACL, tables, 0)) - goto error; /* purecov: inspected */ + if (check_one_table_access(thd, INDEX_ACL, tables)) + goto error; /* purecov: inspected */ if (end_active_trans(thd)) res= -1; else @@ -2156,7 +2758,7 @@ mysql_execute_command(void) break; case SQLCOM_SHOW_DATABASES: #if defined(DONT_ALLOW_SHOW_COMMANDS) - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else if ((specialflag & SPECIAL_SKIP_SHOW_DB) && @@ -2168,9 +2770,19 @@ mysql_execute_command(void) case SQLCOM_SHOW_PROCESSLIST: if (!thd->priv_user[0] && check_global_access(thd,PROCESS_ACL)) break; - mysqld_list_processes(thd,thd->master_access & PROCESS_ACL ? NullS : + mysqld_list_processes(thd, + thd->master_access & PROCESS_ACL ? NullS : thd->priv_user,lex->verbose); break; + case SQLCOM_SHOW_STORAGE_ENGINES: + res= mysqld_show_storage_engines(thd); + break; + case SQLCOM_SHOW_PRIVILEGES: + res= mysqld_show_privileges(thd); + break; + case SQLCOM_SHOW_COLUMN_TYPES: + res= mysqld_show_column_types(thd); + break; case SQLCOM_SHOW_STATUS: res= mysqld_show(thd,(lex->wild ? lex->wild->ptr() : NullS),status_vars, OPT_GLOBAL, &LOCK_status); @@ -2181,34 +2793,41 @@ mysql_execute_command(void) &LOCK_global_system_variables); break; case SQLCOM_SHOW_LOGS: - { - res= mysqld_show_logs(thd); - break; - } +#ifdef DONT_ALLOW_SHOW_COMMANDS + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + DBUG_VOID_RETURN; +#else + { + if (grant_option && check_access(thd, FILE_ACL, any_db,0,0,0)) + goto error; + res= mysqld_show_logs(thd); + break; + } +#endif case SQLCOM_SHOW_TABLES: /* FALL THROUGH */ #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else { char *db=select_lex->db ? select_lex->db : thd->db; if (!db) { - send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: inspected */ + send_error(thd,ER_NO_DB_ERROR); /* purecov: inspected */ goto error; /* purecov: inspected */ } remove_escape(db); // Fix escaped '_' if (check_db_name(db)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, db); + net_printf(thd,ER_WRONG_DB_NAME, db); goto error; } - if (check_access(thd,SELECT_ACL,db,&thd->col_access)) + if (check_access(thd,SELECT_ACL,db,&thd->col_access,0,0)) goto error; /* purecov: inspected */ if (!thd->col_access && check_grant_db(thd,db)) { - net_printf(&thd->net,ER_DBACCESS_DENIED_ERROR, + net_printf(thd, ER_DBACCESS_DENIED_ERROR, thd->priv_user, thd->priv_host, db); @@ -2227,9 +2846,15 @@ mysql_execute_command(void) case SQLCOM_SHOW_OPEN_TABLES: res= mysqld_show_open_tables(thd,(lex->wild ? lex->wild->ptr() : NullS)); break; + case SQLCOM_SHOW_CHARSETS: + res= mysqld_show_charsets(thd,(lex->wild ? lex->wild->ptr() : NullS)); + break; + case SQLCOM_SHOW_COLLATIONS: + res= mysqld_show_collations(thd,(lex->wild ? lex->wild->ptr() : NullS)); + break; case SQLCOM_SHOW_FIELDS: #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else { @@ -2237,9 +2862,9 @@ mysql_execute_command(void) remove_escape(db); // Fix escaped '_' remove_escape(tables->real_name); if (check_access(thd,SELECT_ACL | EXTRA_ACL,db, - &tables->grant.privilege)) + &tables->grant.privilege, 0, 0)) goto error; /* purecov: inspected */ - if (grant_option && check_grant(thd,SELECT_ACL,tables,2)) + if (grant_option && check_grant(thd, SELECT_ACL, tables, 2, UINT_MAX, 0)) goto error; res= mysqld_show_fields(thd,tables, (lex->wild ? lex->wild->ptr() : NullS), @@ -2249,16 +2874,17 @@ mysql_execute_command(void) #endif case SQLCOM_SHOW_KEYS: #ifdef DONT_ALLOW_SHOW_COMMANDS - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ + send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ DBUG_VOID_RETURN; #else { char *db=tables->db; remove_escape(db); // Fix escaped '_' remove_escape(tables->real_name); - if (check_access(thd,SELECT_ACL,db,&tables->grant.privilege)) - goto error; /* purecov: inspected */ - if (grant_option && check_grant(thd,SELECT_ACL,tables,2)) + if (check_access(thd,SELECT_ACL | EXTRA_ACL,db, + &tables->grant.privilege, 0, 0)) + goto error; /* purecov: inspected */ + if (grant_option && check_grant(thd, SELECT_ACL, tables, 2, UINT_MAX, 0)) goto error; res= mysqld_show_keys(thd,tables); break; @@ -2267,14 +2893,15 @@ mysql_execute_command(void) case SQLCOM_CHANGE_DB: mysql_change_db(thd,select_lex->db); break; + case SQLCOM_LOAD: { uint privilege= (lex->duplicates == DUP_REPLACE ? - INSERT_ACL | UPDATE_ACL | DELETE_ACL : INSERT_ACL); + INSERT_ACL | DELETE_ACL : INSERT_ACL); if (!lex->local_file) { - if (check_access(thd,privilege | FILE_ACL,tables->db)) + if (check_access(thd,privilege | FILE_ACL,tables->db,0,0,0)) goto error; } else @@ -2282,20 +2909,27 @@ mysql_execute_command(void) if (!(thd->client_capabilities & CLIENT_LOCAL_FILES) || ! opt_local_infile) { - send_error(&thd->net,ER_NOT_ALLOWED_COMMAND); + send_error(thd,ER_NOT_ALLOWED_COMMAND); goto error; } - if (check_one_table_access(thd, privilege, tables, 0)) + if (check_one_table_access(thd, privilege, tables)) goto error; } res=mysql_load(thd, lex->exchange, tables, lex->field_list, lex->duplicates, (bool) lex->local_file, lex->lock_option); break; } + case SQLCOM_SET_OPTION: - if (!(res=sql_set_variables(thd, &lex->var_list))) - send_ok(&thd->net); + if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || + (res= open_and_lock_tables(thd,tables)))) + break; + if (!(res= sql_set_variables(thd, &lex->var_list))) + send_ok(thd); + if (thd->net.report_error) + res= -1; break; + case SQLCOM_UNLOCK_TABLES: unlock_locked_tables(thd); if (thd->options & OPTION_TABLE_LOCK) @@ -2305,13 +2939,13 @@ mysql_execute_command(void) } if (thd->global_read_lock) unlock_global_read_lock(thd); - send_ok(&thd->net); + send_ok(thd); break; case SQLCOM_LOCK_TABLES: unlock_locked_tables(thd); if (check_db_used(thd,tables) || end_active_trans(thd)) goto error; - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, tables)) + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, tables,0)) goto error; thd->in_lock_tables=1; thd->options|= OPTION_TABLE_LOCK; @@ -2323,7 +2957,7 @@ mysql_execute_command(void) #endif /*HAVE_QUERY_CACHE*/ thd->locked_tables=thd->lock; thd->lock=0; - send_ok(&thd->net); + send_ok(thd); } else thd->options&= ~(ulong) (OPTION_TABLE_LOCK); @@ -2334,7 +2968,7 @@ mysql_execute_command(void) char *alias; if (!(alias=thd->strdup(lex->name)) || check_db_name(lex->name)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, lex->name); + net_printf(thd,ER_WRONG_DB_NAME, lex->name); break; } /* @@ -2344,6 +2978,7 @@ mysql_execute_command(void) do_db/ignore_db. And as this query involves no tables, tables_ok() above was not called. So we have to check rules again here. */ +#ifdef HAVE_REPLICATION if (thd->slave_thread && (!db_ok(lex->name, replicate_do_db, replicate_ignore_db) || !db_ok_with_wild_table(lex->name))) @@ -2351,11 +2986,11 @@ mysql_execute_command(void) my_error(ER_SLAVE_IGNORED_TABLE, MYF(0)); break; } - - if (check_access(thd,CREATE_ACL,lex->name,0,1)) +#endif + if (check_access(thd,CREATE_ACL,lex->name,0,1,0)) break; - res=mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name), - lex->create_info.options,0); + res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name), + &lex->create_info, 0); break; } case SQLCOM_DROP_DB: @@ -2363,7 +2998,7 @@ mysql_execute_command(void) char *alias; if (!(alias=thd->strdup(lex->name)) || check_db_name(lex->name)) { - net_printf(&thd->net,ER_WRONG_DB_NAME, lex->name); + net_printf(thd, ER_WRONG_DB_NAME, lex->name); break; } /* @@ -2373,6 +3008,7 @@ mysql_execute_command(void) do_db/ignore_db. And as this query involves no tables, tables_ok() above was not called. So we have to check rules again here. */ +#ifdef HAVE_REPLICATION if (thd->slave_thread && (!db_ok(lex->name, replicate_do_db, replicate_ignore_db) || !db_ok_with_wild_table(lex->name))) @@ -2380,43 +3016,127 @@ mysql_execute_command(void) my_error(ER_SLAVE_IGNORED_TABLE, MYF(0)); break; } - if (check_access(thd,DROP_ACL,lex->name,0,1)) +#endif + if (check_access(thd,DROP_ACL,lex->name,0,1,0)) break; if (thd->locked_tables || thd->active_transaction()) { - send_error(&thd->net,ER_LOCK_OR_ACTIVE_TRANSACTION); + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION); goto error; } res=mysql_rm_db(thd,alias,lex->drop_if_exists,0); break; } + case SQLCOM_ALTER_DB: + { + if (!strip_sp(lex->name) || check_db_name(lex->name)) + { + net_printf(thd, ER_WRONG_DB_NAME, lex->name); + break; + } + /* + If in a slave thread : + ALTER DATABASE DB may not be preceded by USE DB. + For that reason, maybe db_ok() in sql/slave.cc did not check the + do_db/ignore_db. And as this query involves no tables, tables_ok() + above was not called. So we have to check rules again here. + */ +#ifdef HAVE_REPLICATION + if (thd->slave_thread && + (!db_ok(lex->name, replicate_do_db, replicate_ignore_db) || + !db_ok_with_wild_table(lex->name))) + { + my_error(ER_SLAVE_IGNORED_TABLE, MYF(0)); + break; + } +#endif + if (check_access(thd,ALTER_ACL,lex->name,0,1,0)) + break; + if (thd->locked_tables || thd->active_transaction()) + { + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION); + goto error; + } + res=mysql_alter_db(thd,lex->name,&lex->create_info); + break; + } + case SQLCOM_SHOW_CREATE_DB: + { + if (!strip_sp(lex->name) || check_db_name(lex->name)) + { + net_printf(thd,ER_WRONG_DB_NAME, lex->name); + break; + } + if (check_access(thd,SELECT_ACL,lex->name,0,1,0)) + break; + if (thd->locked_tables || thd->active_transaction()) + { + send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION); + goto error; + } + res=mysqld_show_create_db(thd,lex->name,&lex->create_info); + break; + } case SQLCOM_CREATE_FUNCTION: - if (check_access(thd,INSERT_ACL,"mysql",0,1)) + if (check_access(thd,INSERT_ACL,"mysql",0,1,0)) break; #ifdef HAVE_DLOPEN if (!(res = mysql_create_function(thd,&lex->udf))) - send_ok(&thd->net); + send_ok(thd); #else res= -1; #endif break; case SQLCOM_DROP_FUNCTION: - if (check_access(thd,DELETE_ACL,"mysql",0,1)) + if (check_access(thd,DELETE_ACL,"mysql",0,1,0)) break; #ifdef HAVE_DLOPEN - if (!(res = mysql_drop_function(thd,lex->udf.name))) - send_ok(&thd->net); + if (!(res = mysql_drop_function(thd,&lex->udf.name))) + send_ok(thd); #else res= -1; #endif break; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + case SQLCOM_DROP_USER: + { + if (check_access(thd, GRANT_ACL,"mysql",0,1,0)) + break; + if (!(res= mysql_drop_user(thd, lex->users_list))) + { + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); + } + send_ok(thd); + } + break; + } + case SQLCOM_REVOKE_ALL: + { + if (check_access(thd, GRANT_ACL ,"mysql",0,1,0)) + break; + if (!(res = mysql_revoke_all(thd, lex->users_list))) + { + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); + } + send_ok(thd); + } + break; + } case SQLCOM_REVOKE: case SQLCOM_GRANT: { if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, tables && tables->db ? tables->db : select_lex->db, tables ? &tables->grant.privilege : 0, - tables ? 0 : 1)) + tables ? 0 : 1,0)) goto error; /* @@ -2433,20 +3153,34 @@ mysql_execute_command(void) if (user->password.str && (strcmp(thd->user,user->user.str) || user->host.str && - my_strcasecmp(user->host.str, thd->host_or_ip))) + my_strcasecmp(&my_charset_latin1, + user->host.str, thd->host_or_ip))) { - if (check_access(thd, UPDATE_ACL, "mysql",0,1)) + if (check_access(thd, UPDATE_ACL, "mysql",0,1,0)) goto error; break; // We are allowed to do changes } } } + if (specialflag & SPECIAL_NO_RESOLVE) + { + LEX_USER *user; + List_iterator <LEX_USER> user_list(lex->users_list); + while ((user=user_list++)) + { + if (hostname_requires_resolving(user->host.str)) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_HOSTNAME_WONT_WORK, + ER(ER_WARN_HOSTNAME_WONT_WORK), + user->host.str); + } + } if (tables) { if (grant_option && check_grant(thd, (lex->grant | lex->grant_tot_col | GRANT_ACL), - tables)) + tables, 0, UINT_MAX, 0)) goto error; if (!(res = mysql_table_grant(thd,tables,lex->users_list, lex->columns, lex->grant, @@ -2465,7 +3199,7 @@ mysql_execute_command(void) { if (lex->columns.elements) { - send_error(&thd->net,ER_ILLEGAL_GRANT_FOR_TABLE); + send_error(thd,ER_ILLEGAL_GRANT_FOR_TABLE); res=1; } else @@ -2491,28 +3225,60 @@ mysql_execute_command(void) } break; } - case SQLCOM_FLUSH: +#endif /*!NO_EMBEDDED_ACCESS_CHECKS*/ case SQLCOM_RESET: + /* + RESET commands are never written to the binary log, so we have to + initialize this variable because RESET shares the same code as FLUSH + */ + lex->no_write_to_binlog= 1; + case SQLCOM_FLUSH: + { if (check_global_access(thd,RELOAD_ACL) || check_db_used(thd, tables)) goto error; - /* error sending is deferred to reload_acl_and_cache */ - reload_acl_and_cache(thd, lex->type, tables) ; + /* + reload_acl_and_cache() will tell us if we are allowed to write to the + binlog or not. + */ + bool write_to_binlog; + if (reload_acl_and_cache(thd, lex->type, tables, &write_to_binlog)) + send_error(thd, 0); + else + { + /* + We WANT to write and we CAN write. + ! we write after unlocking the table. + */ + if (!lex->no_write_to_binlog && write_to_binlog) + { + mysql_update_log.write(thd, thd->query, thd->query_length); + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0); + mysql_bin_log.write(&qinfo); + } + } + send_ok(thd); + } break; + } case SQLCOM_KILL: kill_one_thread(thd,lex->thread_id); break; +#ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_SHOW_GRANTS: res=0; if ((thd->priv_user && !strcmp(thd->priv_user,lex->grant_user->user.str)) || - !check_access(thd, SELECT_ACL, "mysql",0,1)) + !check_access(thd, SELECT_ACL, "mysql",0,1,0)) { res = mysql_show_grants(thd,lex->grant_user); } break; +#endif case SQLCOM_HA_OPEN: if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL, tables)) + check_table_access(thd,SELECT_ACL, tables,0)) goto error; res = mysql_ha_open(thd, tables); break; @@ -2550,7 +3316,7 @@ mysql_execute_command(void) thd->options= ((thd->options & (ulong) ~(OPTION_STATUS_NO_TRANS_UPDATE)) | OPTION_BEGIN); thd->server_status|= SERVER_STATUS_IN_TRANS; - send_ok(&thd->net); + send_ok(thd); } break; case SQLCOM_COMMIT: @@ -2564,7 +3330,7 @@ mysql_execute_command(void) thd->server_status&= ~SERVER_STATUS_IN_TRANS; if (!ha_commit(thd)) { - send_ok(&thd->net); + send_ok(thd); } else res= -1; @@ -2584,9 +3350,9 @@ mysql_execute_command(void) message in the error log, so we don't send it. */ if ((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) && !thd->slave_thread) - send_warning(&thd->net,ER_WARNING_NOT_COMPLETE_ROLLBACK,0); + send_warning(thd,ER_WARNING_NOT_COMPLETE_ROLLBACK,0); else - send_ok(&thd->net); + send_ok(thd); } else res= -1; @@ -2596,32 +3362,68 @@ mysql_execute_command(void) if (!ha_rollback_to_savepoint(thd, lex->savepoint_name)) { if ((thd->options & OPTION_STATUS_NO_TRANS_UPDATE) && !thd->slave_thread) - send_warning(&thd->net,ER_WARNING_NOT_COMPLETE_ROLLBACK,0); + send_warning(thd, ER_WARNING_NOT_COMPLETE_ROLLBACK, 0); else - send_ok(&thd->net); + send_ok(thd); } else res= -1; break; case SQLCOM_SAVEPOINT: if (!ha_savepoint(thd, lex->savepoint_name)) - send_ok(&thd->net); + send_ok(thd); else res= -1; break; default: /* Impossible */ - send_ok(&thd->net); + send_ok(thd); break; } thd->proc_info="query end"; // QQ if (res < 0) - send_error(&thd->net,thd->killed ? ER_SERVER_SHUTDOWN : 0); + send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0); error: DBUG_VOID_RETURN; } +/* + Check grants for commands which work only with one table and all other + tables belong to subselects. + + SYNOPSIS + check_one_table_access() + thd Thread handler + privilege requested privelage + tables table list of command + + RETURN + 0 - OK + 1 - access denied, error is sent to client +*/ + +int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables) + +{ + if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0)) + return 1; + + /* Show only 1 table for check_grant */ + if (grant_option && check_grant(thd, privilege, tables, 0, 1, 0)) + return 1; + + /* Check rights on tables of subselect (if exists) */ + TABLE_LIST *subselects_tables; + if ((subselects_tables= tables->next)) + { + if ((check_table_access(thd, SELECT_ACL, subselects_tables,0))) + return 1; + } + return 0; +} + + /**************************************************************************** Get the user (global) and database privileges for all used tables @@ -2648,7 +3450,10 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, DBUG_ENTER("check_access"); DBUG_PRINT("enter",("want_access: %lu master_access: %lu", want_access, thd->master_access)); - ulong db_access,dummy; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + ulong db_access; +#endif + ulong dummy; if (save_priv) *save_priv=0; else @@ -2657,10 +3462,13 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, if ((!db || !db[0]) && !thd->db && !dont_check_global_grants) { if (!no_errors) - send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: tested */ + send_error(thd,ER_NO_DB_ERROR); /* purecov: tested */ DBUG_RETURN(TRUE); /* purecov: tested */ } +#ifdef NO_EMBEDDED_ACCESS_CHECKS + DBUG_RETURN(0); +#else if ((thd->master_access & want_access) == want_access) { /* @@ -2671,8 +3479,8 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, db_access= thd->db_access; if (!(thd->master_access & SELECT_ACL) && (db && (!thd->db || strcmp(db,thd->db)))) - db_access=acl_get(thd->host, thd->ip, (char*) &thd->remote.sin_addr, - thd->priv_user, db); /* purecov: inspected */ + db_access=acl_get(thd->host, thd->ip, + thd->priv_user, db, test(want_access & GRANT_ACL)); *save_priv=thd->master_access | db_access; DBUG_RETURN(FALSE); } @@ -2680,7 +3488,7 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, ! db && dont_check_global_grants) { // We can never grant this if (!no_errors) - net_printf(&thd->net,ER_ACCESS_DENIED_ERROR, + net_printf(thd,ER_ACCESS_DENIED_ERROR, thd->priv_user, thd->priv_host, thd->password ? ER(ER_YES) : ER(ER_NO));/* purecov: tested */ @@ -2691,8 +3499,8 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, DBUG_RETURN(FALSE); // Allow select on anything if (db && (!thd->db || strcmp(db,thd->db))) - db_access=acl_get(thd->host, thd->ip, (char*) &thd->remote.sin_addr, - thd->priv_user, db); /* purecov: inspected */ + db_access=acl_get(thd->host, thd->ip, + thd->priv_user, db, test(want_access & GRANT_ACL)); else db_access=thd->db_access; // Remove SHOW attribute and access rights we already have @@ -2705,11 +3513,12 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, !(want_access & ~(db_access | TABLE_ACLS)))) DBUG_RETURN(FALSE); /* Ok */ if (!no_errors) - net_printf(&thd->net,ER_DBACCESS_DENIED_ERROR, + net_printf(thd,ER_DBACCESS_DENIED_ERROR, thd->priv_user, thd->priv_host, db ? db : thd->db ? thd->db : "unknown"); /* purecov: tested */ DBUG_RETURN(TRUE); /* purecov: tested */ +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ } @@ -2734,13 +3543,17 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, bool check_global_access(THD *thd, ulong want_access) { +#ifdef NO_EMBEDDED_ACCESS_CHECKS + return 0; +#else char command[128]; if ((thd->master_access & want_access)) return 0; get_privilege_desc(command, sizeof(command), want_access); - net_printf(&thd->net,ER_SPECIFIC_ACCESS_DENIED_ERROR, + net_printf(thd,ER_SPECIFIC_ACCESS_DENIED_ERROR, command); return 1; +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ } @@ -2758,6 +3571,8 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, TABLE_LIST *org_tables=tables; for (; tables ; tables=tables->next) { + if (tables->derived || (tables->table && (int)tables->table->tmp_table)) + continue; if ((thd->master_access & want_access) == (want_access & ~EXTRA_ACL) && thd->db) tables->grant.privilege= want_access; @@ -2780,20 +3595,27 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, } if (grant_option) return check_grant(thd,want_access & ~EXTRA_ACL,org_tables, - test(want_access & EXTRA_ACL), no_errors); + test(want_access & EXTRA_ACL), UINT_MAX, no_errors); return FALSE; } - -static bool -check_one_table_access(THD *thd, ulong want_access, TABLE_LIST *table, - bool no_errors) +bool check_merge_table_access(THD *thd, char *db, + TABLE_LIST *table_list) { - if (check_access(thd, want_access, table->db, &table->grant.privilege, 0, - no_errors)) - return 1; - return (grant_option && check_grant(thd, want_access, table, 0, - no_errors)); + int error=0; + if (table_list) + { + /* Check that all tables use the current database */ + TABLE_LIST *tmp; + for (tmp=table_list; tmp ; tmp=tmp->next) + { + if (!tmp->db || !tmp->db[0]) + tmp->db=db; + } + error=check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, + table_list,0); + } + return error; } @@ -2805,7 +3627,7 @@ static bool check_db_used(THD *thd,TABLE_LIST *tables) { if (!(tables->db=thd->db)) { - send_error(&thd->net,ER_NO_DB_ERROR); /* purecov: tested */ + send_error(thd,ER_NO_DB_ERROR); /* purecov: tested */ return TRUE; /* purecov: tested */ } } @@ -2813,32 +3635,6 @@ static bool check_db_used(THD *thd,TABLE_LIST *tables) return FALSE; } - -static bool check_merge_table_access(THD *thd, char *db, - TABLE_LIST *table_list) -{ - int error=0; - if (table_list) - { - /* Check that all tables use the current database */ - TABLE_LIST *tmp; - for (tmp=table_list; tmp ; tmp=tmp->next) - { - if (!tmp->db || !tmp->db[0]) - tmp->db=db; - else if (strcmp(tmp->db,db)) - { - send_error(&thd->net,ER_UNION_TABLES_IN_DIFFERENT_DIR); - return 1; - } - } - error=check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, - table_list); - } - return error; -} - - /**************************************************************************** Check stack size; Send error if there isn't enough stack to continue ****************************************************************************/ @@ -2849,6 +3645,10 @@ static bool check_merge_table_access(THD *thd, char *db, #define used_stack(A,B) (long) (B - A) #endif +#ifndef DBUG_OFF +long max_stack_used; +#endif + #ifndef EMBEDDED_LIBRARY bool check_stack_overrun(THD *thd,char *buf __attribute__((unused))) { @@ -2858,9 +3658,12 @@ bool check_stack_overrun(THD *thd,char *buf __attribute__((unused))) { sprintf(errbuff[0],ER(ER_STACK_OVERRUN),stack_used,thread_stack); my_message(ER_STACK_OVERRUN,errbuff[0],MYF(0)); - thd->fatal_error=1; + thd->fatal_error(); return 1; } +#ifndef DBUG_OFF + max_stack_used= max(max_stack_used, stack_used); +#endif return 0; } #endif /* EMBEDDED_LIBRARY */ @@ -2898,80 +3701,160 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, int *yystacksize) /**************************************************************************** - Initialize global thd variables needed for query + Initialize global thd variables needed for query ****************************************************************************/ -static void +void mysql_init_query(THD *thd) { DBUG_ENTER("mysql_init_query"); - thd->lex.select_lex.item_list.empty(); - thd->lex.value_list.empty(); - thd->lex.select_lex.table_list.elements=0; - thd->free_list=0; thd->lex.union_option=0; - thd->lex.select = &thd->lex.select_lex; - thd->lex.select_lex.table_list.first=0; - thd->lex.select_lex.table_list.next= (byte**) &thd->lex.select_lex.table_list.first; - thd->lex.select_lex.next=0; - /* - select_lex.options is also inited in dispatch_command(), but for - replication (which bypasses dispatch_command() and calls mysql_parse() - directly) we must do it here. - */ - thd->lex.select_lex.options=0; - thd->lex.olap=0; - thd->lex.select->olap= UNSPECIFIED_OLAP_TYPE; - thd->fatal_error=0; // Safety - thd->last_insert_id_used=thd->query_start_used=thd->insert_id_used=0; - thd->rand_used=0; - thd->sent_row_count=thd->examined_row_count=0; - thd->safe_to_cache_query=1; + LEX *lex= thd->lex; + lex->unit.init_query(); + lex->unit.init_select(); + lex->unit.thd= thd; + lex->select_lex.init_query(); + lex->value_list.empty(); + lex->param_list.empty(); + lex->unit.next= lex->unit.master= + lex->unit.link_next= lex->unit.return_to=0; + lex->unit.prev= lex->unit.link_prev= 0; + lex->unit.slave= lex->unit.global_parameters= lex->current_select= + lex->all_selects_list= &lex->select_lex; + lex->select_lex.master= &lex->unit; + lex->select_lex.prev= &lex->unit.slave; + lex->select_lex.link_next= lex->select_lex.slave= lex->select_lex.next= 0; + lex->select_lex.link_prev= (st_select_lex_node**)&(lex->all_selects_list); + lex->select_lex.options=0; + lex->describe= 0; + lex->derived_tables= FALSE; + lex->lock_option= TL_READ; + lex->found_colon= 0; + lex->safe_to_cache_query= 1; + thd->select_number= lex->select_lex.select_number= 1; + thd->free_list= 0; + thd->total_warn_count=0; // Warnings for this query + thd->last_insert_id_used= thd->query_start_used= thd->insert_id_used=0; + thd->sent_row_count= thd->examined_row_count= 0; + thd->is_fatal_error= thd->rand_used= 0; + thd->server_status&= ~ (SERVER_MORE_RESULTS_EXISTS | + SERVER_QUERY_NO_INDEX_USED | + SERVER_QUERY_NO_GOOD_INDEX_USED); + thd->tmp_table_used= 0; + if (opt_bin_log) + reset_dynamic(&thd->user_var_events); + thd->clear_error(); DBUG_VOID_RETURN; } + void mysql_init_select(LEX *lex) { - SELECT_LEX *select_lex = lex->select; - select_lex->where=select_lex->having=0; - select_lex->select_limit= lex->thd->variables.select_limit; - select_lex->offset_limit=0; - select_lex->options=0; - select_lex->linkage=UNSPECIFIED_TYPE; - select_lex->olap= UNSPECIFIED_OLAP_TYPE; - lex->exchange = 0; - lex->proc_list.first=0; - select_lex->order_list.empty(); - select_lex->group_list.empty(); - select_lex->next = (SELECT_LEX *)NULL; + SELECT_LEX *select_lex= lex->current_select; + select_lex->init_select(); + select_lex->select_limit= HA_POS_ERROR; + if (select_lex == &lex->select_lex) + { + lex->exchange= 0; + lex->result= 0; + lex->proc_list.first= 0; + } } bool -mysql_new_select(LEX *lex) +mysql_new_select(LEX *lex, bool move_down) { - SELECT_LEX *select_lex = (SELECT_LEX *) lex->thd->calloc(sizeof(SELECT_LEX)); + SELECT_LEX *select_lex = new(&lex->thd->mem_root) SELECT_LEX(); if (!select_lex) return 1; - lex->select->next=select_lex; - lex->select=select_lex; - select_lex->table_list.next= (byte**) &select_lex->table_list.first; - select_lex->item_list.empty(); - select_lex->when_list.empty(); - select_lex->expr_list.empty(); - select_lex->interval_list.empty(); - select_lex->use_index.empty(); - select_lex->ftfunc_list.empty(); + select_lex->select_number= ++lex->thd->select_number; + select_lex->init_query(); + select_lex->init_select(); + if (move_down) + { + /* first select_lex of subselect or derived table */ + SELECT_LEX_UNIT *unit= new(&lex->thd->mem_root) SELECT_LEX_UNIT(); + if (!unit) + return 1; + unit->init_query(); + unit->init_select(); + unit->thd= lex->thd; + unit->include_down(lex->current_select); + unit->link_next= 0; + unit->link_prev= 0; + unit->return_to= lex->current_select; + select_lex->include_down(unit); + // TODO: assign resolve_mode for fake subquery after merging with new tree + } + else + { + select_lex->include_neighbour(lex->current_select); + SELECT_LEX_UNIT *unit= select_lex->master_unit(); + SELECT_LEX *fake= unit->fake_select_lex; + if (!fake) + { + /* + as far as we included SELECT_LEX for UNION unit should have + fake SELECT_LEX for UNION processing + */ + fake= unit->fake_select_lex= new(&lex->thd->mem_root) SELECT_LEX(); + fake->include_standalone(unit, + (SELECT_LEX_NODE**)&unit->fake_select_lex); + fake->select_number= INT_MAX; + fake->make_empty_select(); + fake->linkage= GLOBAL_OPTIONS_TYPE; + fake->select_limit= HA_POS_ERROR; + } + } + + select_lex->master_unit()->global_parameters= select_lex; + select_lex->include_global((st_select_lex_node**)&lex->all_selects_list); + lex->current_select= select_lex; + select_lex->resolve_mode= SELECT_LEX::SELECT_MODE; return 0; } +/* + Create a select to return the same output as 'SELECT @@var_name'. + + SYNOPSIS + create_select_for_variable() + var_name Variable name + + DESCRIPTION + Used for SHOW COUNT(*) [ WARNINGS | ERROR] + + This will crash with a core dump if the variable doesn't exists +*/ + +void create_select_for_variable(const char *var_name) +{ + THD *thd; + LEX *lex; + LEX_STRING tmp, null_lex_string; + DBUG_ENTER("create_select_for_variable"); + + thd= current_thd; + lex= thd->lex; + mysql_init_select(lex); + lex->sql_command= SQLCOM_SELECT; + tmp.str= (char*) var_name; + tmp.length=strlen(var_name); + bzero((char*) &null_lex_string.str, sizeof(null_lex_string)); + add_item_to_list(thd, get_system_var(thd, OPT_SESSION, tmp, + null_lex_string)); + DBUG_VOID_RETURN; +} + void mysql_init_multi_delete(LEX *lex) { - lex->sql_command = SQLCOM_DELETE_MULTI; + lex->sql_command= SQLCOM_DELETE_MULTI; mysql_init_select(lex); - lex->select->select_limit=lex->thd->select_limit=HA_POS_ERROR; - lex->select->table_list.save_and_clear(&lex->auxilliary_table_list); + lex->select_lex.select_limit= lex->unit.select_limit_cnt= + HA_POS_ERROR; + lex->select_lex.table_list.save_and_clear(&lex->auxilliary_table_list); } @@ -2979,41 +3862,50 @@ void mysql_init_multi_delete(LEX *lex) When you modify mysql_parse(), you may need to mofify mysql_test_parse_for_slave() in this same file. */ + void mysql_parse(THD *thd, char *inBuf, uint length) { DBUG_ENTER("mysql_parse"); - thd->query_length = length; mysql_init_query(thd); if (query_cache_send_result_to_client(thd, inBuf, length) <= 0) { LEX *lex=lex_start(thd, (uchar*) inBuf, length); - if (!yyparse() && ! thd->fatal_error) + if (!yyparse((void *)thd) && ! thd->is_fatal_error) { +#ifndef NO_EMBEDDED_ACCESS_CHECKS if (mqh_used && thd->user_connect && - check_mqh(thd, thd->lex.sql_command)) + check_mqh(thd, lex->sql_command)) { thd->net.error = 0; } else +#endif { - mysql_execute_command(); - query_cache_end_of_result(&thd->net); + if (thd->net.report_error) + send_error(thd, 0, NullS); + else + { + mysql_execute_command(thd); + query_cache_end_of_result(thd); + } } } else { DBUG_PRINT("info",("Command aborted. Fatal_error: %d", - thd->fatal_error)); + thd->is_fatal_error)); query_cache_abort(&thd->net); } thd->proc_info="freeing items"; - free_items(thd); /* Free strings used by items */ + free_items(thd->free_list); /* Free strings used by items */ lex_end(lex); } DBUG_VOID_RETURN; } + +#ifdef HAVE_REPLICATION /* Usable by the replication SQL thread only: just parse a query to know if it can be ignored because of replicate-*-table rules. @@ -3030,86 +3922,99 @@ bool mysql_test_parse_for_slave(THD *thd, char *inBuf, uint length) mysql_init_query(thd); lex= lex_start(thd, (uchar*) inBuf, length); - if (!yyparse() && ! thd->fatal_error && + if (!yyparse((void*) thd) && ! thd->is_fatal_error && all_tables_not_ok(thd,(TABLE_LIST*) lex->select_lex.table_list.first)) error= 1; /* Ignore question */ - free_items(thd); /* Free strings used by items */ + free_items(thd->free_list); /* Free strings used by items */ lex_end(lex); return error; } - +#endif /***************************************************************************** ** Store field definition for create ** Return 0 if ok ******************************************************************************/ -bool add_field_to_list(char *field_name, enum_field_types type, +bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, char *length, char *decimals, - uint type_modifier, Item *default_value,char *change, - TYPELIB *interval) + uint type_modifier, + Item *default_value, Item *on_update_value, + LEX_STRING *comment, + char *change, TYPELIB *interval, CHARSET_INFO *cs, + uint uint_geom_type) { register create_field *new_field; - THD *thd=current_thd; - LEX *lex= &thd->lex; + LEX *lex= thd->lex; uint allowed_type_modifier=0; + char warn_buff[MYSQL_ERRMSG_SIZE]; DBUG_ENTER("add_field_to_list"); if (strlen(field_name) > NAME_LEN) { - net_printf(&thd->net, ER_TOO_LONG_IDENT, field_name); /* purecov: inspected */ + net_printf(thd, ER_TOO_LONG_IDENT, field_name); /* purecov: inspected */ DBUG_RETURN(1); /* purecov: inspected */ } if (type_modifier & PRI_KEY_FLAG) { lex->col_list.push_back(new key_part_spec(field_name,0)); - lex->key_list.push_back(new Key(Key::PRIMARY,NullS, - lex->col_list)); + lex->key_list.push_back(new Key(Key::PRIMARY, NullS, HA_KEY_ALG_UNDEF, + 0, lex->col_list)); lex->col_list.empty(); } if (type_modifier & (UNIQUE_FLAG | UNIQUE_KEY_FLAG)) { lex->col_list.push_back(new key_part_spec(field_name,0)); - lex->key_list.push_back(new Key(Key::UNIQUE,NullS, + lex->key_list.push_back(new Key(Key::UNIQUE, NullS, HA_KEY_ALG_UNDEF, 0, lex->col_list)); lex->col_list.empty(); } if (default_value) { - /* - We allow specifying value for first TIMESTAMP column - altough it is silently ignored. This should be fixed in 4.1 - (by proper warning or real support for default values) + /* + Default value should be literal => basic constants => + no need fix_fields() + + We allow only one function as part of default value - + NOW() as default for TIMESTAMP type. */ - if (default_value->type() == Item::NULL_ITEM) + if (default_value->type() == Item::FUNC_ITEM && + !(((Item_func*)default_value)->functype() == Item_func::NOW_FUNC && + type == FIELD_TYPE_TIMESTAMP)) + { + net_printf(thd, ER_INVALID_DEFAULT, field_name); + DBUG_RETURN(1); + } + else if (default_value->type() == Item::NULL_ITEM) { default_value=0; if ((type_modifier & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) { - net_printf(&thd->net,ER_INVALID_DEFAULT,field_name); + net_printf(thd,ER_INVALID_DEFAULT,field_name); DBUG_RETURN(1); } } -#ifdef MYSQL41000 else if (type_modifier & AUTO_INCREMENT_FLAG) { - net_printf(&thd->net, ER_INVALID_DEFAULT, field_name); + net_printf(thd, ER_INVALID_DEFAULT, field_name); DBUG_RETURN(1); } -#endif } + + if (on_update_value && type != FIELD_TYPE_TIMESTAMP) + { + net_printf(thd, ER_INVALID_ON_UPDATE, field_name); + DBUG_RETURN(1); + } + if (!(new_field=new create_field())) DBUG_RETURN(1); new_field->field=0; new_field->field_name=field_name; -#ifdef MYSQL41000 new_field->def= default_value; -#else - new_field->def= (type_modifier & AUTO_INCREMENT_FLAG ? 0 : default_value); -#endif new_field->flags= type_modifier; new_field->unireg_check= (type_modifier & AUTO_INCREMENT_FLAG ? Field::NEXT_NUMBER : Field::NONE); @@ -3120,6 +4025,20 @@ bool add_field_to_list(char *field_name, enum_field_types type, new_field->change=change; new_field->interval=0; new_field->pack_length=0; + new_field->charset=cs; + new_field->geom_type= (Field::geometry_type) uint_geom_type; + + if (!comment) + { + new_field->comment.str=0; + new_field->comment.length=0; + } + else + { + /* In this case comment is always of type Item_string */ + new_field->comment.str= (char*) comment->str; + new_field->comment.length=comment->length; + } if (length && !(new_field->length= (uint) atoi(length))) length=0; /* purecov: inspected */ uint sign_len=type_modifier & UNSIGNED_FLAG ? 0 : 1; @@ -3150,10 +4069,7 @@ bool add_field_to_list(char *field_name, enum_field_types type, if (!length) new_field->length=20; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; - case FIELD_TYPE_STRING: - case FIELD_TYPE_VAR_STRING: case FIELD_TYPE_NULL: - case FIELD_TYPE_GEOMETRY: break; case FIELD_TYPE_DECIMAL: if (!length) @@ -3165,17 +4081,43 @@ bool add_field_to_list(char *field_name, enum_field_types type, new_field->length++; } break; + case FIELD_TYPE_STRING: + case FIELD_TYPE_VAR_STRING: + if (new_field->length <= MAX_FIELD_CHARLENGTH || default_value) + break; + /* Convert long CHAR() and VARCHAR columns to TEXT or BLOB */ + new_field->sql_type= FIELD_TYPE_BLOB; + sprintf(warn_buff, ER(ER_AUTO_CONVERT), field_name, "CHAR", + (cs == &my_charset_bin) ? "BLOB" : "TEXT"); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_AUTO_CONVERT, + warn_buff); + /* fall through */ case FIELD_TYPE_BLOB: case FIELD_TYPE_TINY_BLOB: case FIELD_TYPE_LONG_BLOB: case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_GEOMETRY: + if (new_field->length) + { + /* The user has given a length to the blob column */ + if (new_field->length < 256) + type= FIELD_TYPE_TINY_BLOB; + if (new_field->length < 65536) + type= FIELD_TYPE_BLOB; + else if (new_field->length < 256L*256L*256L) + type= FIELD_TYPE_MEDIUM_BLOB; + else + type= FIELD_TYPE_LONG_BLOB; + new_field->length= 0; + } + new_field->sql_type= type; if (default_value) // Allow empty as default value { String str,*res; res=default_value->val_str(&str); if (res->length()) { - net_printf(&thd->net,ER_BLOB_CANT_HAVE_DEFAULT,field_name); /* purecov: inspected */ + net_printf(thd,ER_BLOB_CANT_HAVE_DEFAULT,field_name); /* purecov: inspected */ DBUG_RETURN(1); /* purecov: inspected */ } new_field->def=0; @@ -3195,7 +4137,7 @@ bool add_field_to_list(char *field_name, enum_field_types type, uint tmp_length=new_field->length; if (tmp_length > PRECISION_FOR_DOUBLE) { - net_printf(&thd->net,ER_WRONG_FIELD_SPEC,field_name); + net_printf(thd,ER_WRONG_FIELD_SPEC,field_name); DBUG_RETURN(1); } else if (tmp_length > PRECISION_FOR_FLOAT) @@ -3231,6 +4173,34 @@ bool add_field_to_list(char *field_name, enum_field_types type, new_field->length= min(new_field->length,14); /* purecov: inspected */ } new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG | NOT_NULL_FLAG; + if (default_value) + { + /* Grammar allows only NOW() value for ON UPDATE clause */ + if (default_value->type() == Item::FUNC_ITEM && + ((Item_func*)default_value)->functype() == Item_func::NOW_FUNC) + { + new_field->unireg_check= (on_update_value?Field::TIMESTAMP_DNUN_FIELD: + Field::TIMESTAMP_DN_FIELD); + /* + We don't need default value any longer moreover it is dangerous. + Everything handled by unireg_check further. + */ + new_field->def= 0; + } + else + new_field->unireg_check= (on_update_value?Field::TIMESTAMP_UN_FIELD: + Field::NONE); + } + else + { + /* + We are setting TIMESTAMP_OLD_FIELD here only temporary, we will + replace this value by TIMESTAMP_DNUN_FIELD or NONE later when + information about all TIMESTAMP fields in table will be availiable. + */ + new_field->unireg_check= on_update_value?Field::TIMESTAMP_UN_FIELD: + Field::TIMESTAMP_OLD_FIELD; + } break; case FIELD_TYPE_DATE: // Old date type if (protocol_version != PROTOCOL_VERSION-1) @@ -3249,7 +4219,7 @@ bool add_field_to_list(char *field_name, enum_field_types type, { if (interval->count > sizeof(longlong)*8) { - net_printf(&thd->net,ER_TOO_BIG_SET,field_name); /* purecov: inspected */ + net_printf(thd,ER_TOO_BIG_SET,field_name); /* purecov: inspected */ DBUG_RETURN(1); /* purecov: inspected */ } new_field->pack_length=(interval->count+7)/8; @@ -3265,13 +4235,18 @@ bool add_field_to_list(char *field_name, enum_field_types type, set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1); if (default_value) { + char *not_used; + uint not_used2; + bool not_used3; + thd->cuted_fields=0; String str,*res; res=default_value->val_str(&str); - (void) find_set(interval,res->ptr(),res->length()); + (void) find_set(interval, res->ptr(), res->length(), ¬_used, + ¬_used2, ¬_used3); if (thd->cuted_fields) { - net_printf(&thd->net,ER_INVALID_DEFAULT,field_name); + net_printf(thd,ER_INVALID_DEFAULT,field_name); DBUG_RETURN(1); } } @@ -3292,9 +4267,10 @@ bool add_field_to_list(char *field_name, enum_field_types type, { String str,*res; res=default_value->val_str(&str); - if (!find_enum(interval,res->ptr(),res->length())) + res->strip_sp(); + if (!find_type(interval, res->ptr(), res->length(), 0)) { - net_printf(&thd->net,ER_INVALID_DEFAULT,field_name); + net_printf(thd,ER_INVALID_DEFAULT,field_name); DBUG_RETURN(1); } } @@ -3302,18 +4278,20 @@ bool add_field_to_list(char *field_name, enum_field_types type, } } - if (new_field->length >= MAX_FIELD_WIDTH || + if ((new_field->length > MAX_FIELD_CHARLENGTH && type != FIELD_TYPE_SET && + type != FIELD_TYPE_ENUM) || (!new_field->length && !(new_field->flags & BLOB_FLAG) && - type != FIELD_TYPE_STRING && type != FIELD_TYPE_VAR_STRING)) + type != FIELD_TYPE_STRING && + type != FIELD_TYPE_VAR_STRING && type != FIELD_TYPE_GEOMETRY)) { - net_printf(&thd->net,ER_TOO_BIG_FIELDLENGTH,field_name, - MAX_FIELD_WIDTH-1); /* purecov: inspected */ + net_printf(thd,ER_TOO_BIG_FIELDLENGTH,field_name, + MAX_FIELD_CHARLENGTH); /* purecov: inspected */ DBUG_RETURN(1); /* purecov: inspected */ } type_modifier&= AUTO_INCREMENT_FLAG; if ((~allowed_type_modifier) & type_modifier) { - net_printf(&thd->net,ER_WRONG_FIELD_SPEC,field_name); + net_printf(thd,ER_WRONG_FIELD_SPEC,field_name); DBUG_RETURN(1); } if (!new_field->pack_length) @@ -3340,13 +4318,13 @@ add_proc_to_list(THD* thd, Item *item) ORDER *order; Item **item_ptr; - if (!(order = (ORDER *) sql_alloc(sizeof(ORDER)+sizeof(Item*)))) + if (!(order = (ORDER *) thd->alloc(sizeof(ORDER)+sizeof(Item*)))) return 1; item_ptr = (Item**) (order+1); *item_ptr= item; order->item=item_ptr; order->free_me=0; - thd->lex.proc_list.link_in_list((byte*) order,(byte**) &order->next); + thd->lex->proc_list.link_in_list((byte*) order,(byte**) &order->next); return 0; } @@ -3366,8 +4344,8 @@ static void remove_escape(char *name) #ifdef USE_MB int l; /* if ((l = ismbchar(name, name+MBMAXLEN))) { Wei He: I think it's wrong */ - if (use_mb(default_charset_info) && - (l = my_ismbchar(default_charset_info, name, strend))) + if (use_mb(system_charset_info) && + (l = my_ismbchar(system_charset_info, name, strend))) { while (l--) *to++ = *name++; @@ -3387,16 +4365,14 @@ static void remove_escape(char *name) ****************************************************************************/ -bool add_to_list(SQL_LIST &list,Item *item,bool asc) +bool add_to_list(THD *thd, SQL_LIST &list,Item *item,bool asc) { ORDER *order; - Item **item_ptr; DBUG_ENTER("add_to_list"); - if (!(order = (ORDER *) sql_alloc(sizeof(ORDER)+sizeof(Item*)))) + if (!(order = (ORDER *) thd->alloc(sizeof(ORDER)))) DBUG_RETURN(1); - item_ptr = (Item**) (order+1); - *item_ptr=item; - order->item= item_ptr; + order->item_ptr= item; + order->item= &order->item_ptr; order->asc = asc; order->free_me=0; order->used=0; @@ -3424,14 +4400,16 @@ bool add_to_list(SQL_LIST &list,Item *item,bool asc) # Pointer to TABLE_LIST element added to the total table list */ -TABLE_LIST *add_table_to_list(Table_ident *table, LEX_STRING *alias, - ulong table_options, - thr_lock_type lock_type, - List<String> *use_index, - List<String> *ignore_index) +TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, + Table_ident *table, + LEX_STRING *alias, + ulong table_options, + thr_lock_type lock_type, + List<String> *use_index_arg, + List<String> *ignore_index_arg, + LEX_STRING *option) { register TABLE_LIST *ptr; - THD *thd=current_thd; char *alias_str; DBUG_ENTER("add_table_to_list"); @@ -3441,17 +4419,23 @@ TABLE_LIST *add_table_to_list(Table_ident *table, LEX_STRING *alias, if (check_table_name(table->table.str,table->table.length) || table->db.str && check_db_name(table->db.str)) { - net_printf(&thd->net,ER_WRONG_TABLE_NAME,table->table.str); + net_printf(thd, ER_WRONG_TABLE_NAME, table->table.str); DBUG_RETURN(0); } if (!alias) /* Alias is case sensitive */ + { + if (table->sel) + { + net_printf(thd,ER_DERIVED_MUST_HAVE_ALIAS); + DBUG_RETURN(0); + } if (!(alias_str=thd->memdup(alias_str,table->table.length+1))) DBUG_RETURN(0); - + } if (!(ptr = (TABLE_LIST *) thd->calloc(sizeof(TABLE_LIST)))) DBUG_RETURN(0); /* purecov: inspected */ - if (table->db.str) + if (table->db.str) { ptr->db= table->db.str; ptr->db_length= table->db.length; @@ -3463,42 +4447,49 @@ TABLE_LIST *add_table_to_list(Table_ident *table, LEX_STRING *alias, } else { - ptr->db= (char*) ""; + /* The following can't be "" as we may do 'casedn_str()' on it */ + ptr->db= empty_c_string; ptr->db_length= 0; } - + ptr->alias= alias_str; - table_case_convert(table->table.str, table->table.length); + if (lower_case_table_names && table->table.length) + my_casedn_str(files_charset_info, table->table.str); ptr->real_name=table->table.str; ptr->real_name_length=table->table.length; - ptr->lock_type= lock_type; + ptr->lock_type= lock_type; ptr->updating= test(table_options & TL_OPTION_UPDATING); ptr->force_index= test(table_options & TL_OPTION_FORCE_INDEX); - if (use_index) - ptr->use_index=(List<String> *) thd->memdup((gptr) use_index, - sizeof(*use_index)); - if (ignore_index) - ptr->ignore_index=(List<String> *) thd->memdup((gptr) ignore_index, - sizeof(*ignore_index)); - + ptr->ignore_leaves= test(table_options & TL_OPTION_IGNORE_LEAVES); + ptr->derived= table->sel; + ptr->cacheable_table= 1; + if (use_index_arg) + ptr->use_index=(List<String> *) thd->memdup((gptr) use_index_arg, + sizeof(*use_index_arg)); + if (ignore_index_arg) + ptr->ignore_index=(List<String> *) thd->memdup((gptr) ignore_index_arg, + sizeof(*ignore_index_arg)); + ptr->option= option ? option->str : 0; /* check that used name is unique */ if (lock_type != TL_IGNORE) { - for (TABLE_LIST *tables=(TABLE_LIST*) thd->lex.select->table_list.first ; + for (TABLE_LIST *tables=(TABLE_LIST*) table_list.first ; tables ; tables=tables->next) { - if (!strcmp(alias_str,tables->alias) && !strcmp(ptr->db, tables->db)) + if (!my_strcasecmp(table_alias_charset, alias_str, tables->alias) && + !strcmp(ptr->db, tables->db)) { - net_printf(&thd->net,ER_NONUNIQ_TABLE,alias_str); /* purecov: tested */ + net_printf(thd,ER_NONUNIQ_TABLE,alias_str); /* purecov: tested */ DBUG_RETURN(0); /* purecov: tested */ } } } - thd->lex.select->table_list.link_in_list((byte*) ptr,(byte**) &ptr->next); + table_list.link_in_list((byte*) ptr, (byte**) &ptr->next); DBUG_RETURN(ptr); } + /* Set lock for all tables in current select level @@ -3512,15 +4503,14 @@ TABLE_LIST *add_table_to_list(Table_ident *table, LEX_STRING *alias, query */ -void set_lock_for_tables(thr_lock_type lock_type) +void st_select_lex::set_lock_for_tables(thr_lock_type lock_type) { - THD *thd=current_thd; bool for_update= lock_type >= TL_READ_NO_INSERT; DBUG_ENTER("set_lock_for_tables"); DBUG_PRINT("enter", ("lock_type: %d for_update: %d", lock_type, for_update)); - for (TABLE_LIST *tables= (TABLE_LIST*) thd->lex.select->table_list.first ; + for (TABLE_LIST *tables= (TABLE_LIST*) table_list.first ; tables ; tables=tables->next) { @@ -3531,71 +4521,6 @@ void set_lock_for_tables(thr_lock_type lock_type) } -/* -** This is used for UNION to create a new table list of all used tables -** The table_list->table entry in all used tables are set to point -** to the entries in this list. -*/ - -static bool create_total_list(THD *thd, LEX *lex, - TABLE_LIST **result, bool skip_first) -{ - /* Handle the case when we are not using union */ - if (!lex->select_lex.next) - { - *result= (TABLE_LIST*) lex->select_lex.table_list.first; - return 0; - } - - /* We should skip first table if SQL command is SQLCOM_CREATE_TABLE */ - SELECT_LEX *sl; - TABLE_LIST **new_table_list= result, *aux; - - *new_table_list=0; // end result list - for (sl= &lex->select_lex; sl; sl=sl->next) - { - if (sl->order_list.first && sl->next && !sl->braces) - { - net_printf(&thd->net,ER_WRONG_USAGE,"UNION","ORDER BY"); - return 1; - } - if ((aux= (TABLE_LIST*) sl->table_list.first)) - { - TABLE_LIST *next; - for (; aux; aux=next) - { - TABLE_LIST *cursor= *result; - if (skip_first && cursor) - cursor= cursor->next; - next= aux->next; - for ( ; cursor; cursor=cursor->next) - if (!strcmp(cursor->db,aux->db) && - !strcmp(cursor->real_name,aux->real_name) && - !strcmp(cursor->alias, aux->alias)) - break; - if (!cursor) - { - /* Add not used table to the total table list */ - if (!(cursor = (TABLE_LIST *) thd->memdup((char*) aux, - sizeof(*aux)))) - { - send_error(&thd->net,0); - return 1; - } - *new_table_list= cursor; - new_table_list= &cursor->next; - *new_table_list=0; // end result list - } - else - aux->shared=1; // Mark that it's used twice - aux->table= my_reinterpret_cast(TABLE *) (cursor); - } - } - } - return 0; -} - - void add_join_on(TABLE_LIST *b,Item *expr) { if (expr) @@ -3635,26 +4560,33 @@ void add_join_natural(TABLE_LIST *a,TABLE_LIST *b) b->natural_join=a; } - /* Check if name is used in table list */ - -bool check_dup(const char *db, const char *name, TABLE_LIST *tables) -{ - for (; tables ; tables=tables->next) - if (!strcmp(name,tables->real_name) && !strcmp(db,tables->db)) - return 1; - return 0; -} +/* + Reload/resets privileges and the different caches. + SYNOPSIS + reload_acl_and_cache() + thd Thread handler + options What should be reset/reloaded (tables, privileges, + slave...) + tables Tables to flush (if any) + write_to_binlog Depending on 'options', it may be very bad to write the + query to the binlog (e.g. FLUSH SLAVE); this is a + pointer where, if it is not NULL, reload_acl_and_cache() + will put 0 if it thinks we really should not write to + the binlog. Otherwise it will put 1. -/* - Reload/resets privileges and the different caches + RETURN + 0 ok + !=0 error */ -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) +bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, + bool *write_to_binlog) { bool result=0; - bool error_already_sent=0; select_errors=0; /* Write if more errors */ + bool tmp_write_to_binlog= 1; +#ifndef NO_EMBEDDED_ACCESS_CHECKS if (options & REFRESH_GRANT) { acl_reload(thd); @@ -3662,20 +4594,35 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) if (mqh_used) reset_mqh(thd,(LEX_USER *) NULL,true); } +#endif if (options & REFRESH_LOG) { /* Flush the normal query log, the update log, the binary log, the slow query log, and the relay log (if it exists). */ + + /* + Writing this command to the binlog may result in infinite loops when doing + mysqlbinlog|mysql, and anyway it does not really make sense to log it + automatically (would cause more trouble to users than it would help them) + */ + tmp_write_to_binlog= 0; mysql_log.new_file(1); mysql_update_log.new_file(1); mysql_bin_log.new_file(1); mysql_slow_log.new_file(1); +#ifdef HAVE_REPLICATION + if (mysql_bin_log.is_open() && expire_logs_days) + { + long purge_time= time(0) - expire_logs_days*24*60*60; + if (purge_time >= 0) + mysql_bin_log.purge_logs_before_date(purge_time); + } pthread_mutex_lock(&LOCK_active_mi); rotate_relay_log(active_mi); pthread_mutex_unlock(&LOCK_active_mi); - +#endif if (ha_flush_logs()) result=1; if (flush_error_log()) @@ -3692,10 +4639,19 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) query_cache.flush(); // RESET QUERY CACHE } #endif /*HAVE_QUERY_CACHE*/ - if (options & (REFRESH_TABLES | REFRESH_READ_LOCK)) + /* + Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too + (see sql_yacc.yy) + */ + if (options & (REFRESH_TABLES | REFRESH_READ_LOCK)) { if ((options & REFRESH_READ_LOCK) && thd) { + /* + Writing to the binlog could cause deadlocks, as we don't log + UNLOCK TABLES + */ + tmp_write_to_binlog= 0; if (lock_global_read_lock(thd)) return 1; } @@ -3707,9 +4663,14 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) refresh_status(); if (options & REFRESH_THREADS) flush_thread_cache(); +#ifdef HAVE_REPLICATION if (options & REFRESH_MASTER) + { + tmp_write_to_binlog= 0; if (reset_master(thd)) result=1; + } +#endif #ifdef OPENSSL if (options & REFRESH_DES_KEY_FILE) { @@ -3717,37 +4678,23 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) result=load_des_key_file(des_key_file); } #endif +#ifdef HAVE_REPLICATION if (options & REFRESH_SLAVE) { + tmp_write_to_binlog= 0; pthread_mutex_lock(&LOCK_active_mi); if (reset_slave(thd, active_mi)) - { result=1; - /* - reset_slave() sends error itself. - If it didn't, one would either change reset_slave()'s prototype, to - pass *errorcode and *errmsg to it when it's called or - change reset_slave to use my_error() to register the error. - */ - error_already_sent=1; - } pthread_mutex_unlock(&LOCK_active_mi); } +#endif if (options & REFRESH_USER_RESOURCES) reset_mqh(thd,(LEX_USER *) NULL); - - if (thd && !error_already_sent) - { - if (result) - send_error(&thd->net,0); - else - send_ok(&thd->net); - } - + if (write_to_binlog) + *write_to_binlog= tmp_write_to_binlog; return result; } - /* kill on thread @@ -3789,24 +4736,33 @@ void kill_one_thread(THD *thd, ulong id) } if (!error) - send_ok(&thd->net); + send_ok(thd); else - net_printf(&thd->net,error,id); + net_printf(thd,error,id); } /* Clear most status variables */ static void refresh_status(void) { - pthread_mutex_lock(&THR_LOCK_keycache); pthread_mutex_lock(&LOCK_status); for (struct show_var_st *ptr=status_vars; ptr->name; ptr++) { if (ptr->type == SHOW_LONG) - *(ulong*) ptr->value=0; + *(ulong*) ptr->value= 0; + else if (ptr->type == SHOW_KEY_CACHE_LONG) + { + /* + Reset value in 'default' key cache. + This needs to be recoded when we have thread specific key values + */ + char *value= (((char*) sql_key_cache) + + (uint) ((char*) (ptr->value) - + (char*) &dflt_key_cache_var)); + *(ulong*) value= 0; + } } pthread_mutex_unlock(&LOCK_status); - pthread_mutex_unlock(&THR_LOCK_keycache); } @@ -3836,6 +4792,7 @@ static bool append_file_to_dir(THD *thd, const char **filename_ptr, return 0; } + /* Check if the select is a simple select (not an union) @@ -3850,13 +4807,432 @@ static bool append_file_to_dir(THD *thd, const char **filename_ptr, bool check_simple_select() { THD *thd= current_thd; - if (thd->lex.select != &thd->lex.select_lex) + if (thd->lex->current_select != &thd->lex->select_lex) { char command[80]; - strmake(command, thd->lex.yylval->symbol.str, - min(thd->lex.yylval->symbol.length, sizeof(command)-1)); - net_printf(&thd->net, ER_CANT_USE_OPTION_HERE, command); + strmake(command, thd->lex->yylval->symbol.str, + min(thd->lex->yylval->symbol.length, sizeof(command)-1)); + net_printf(thd, ER_CANT_USE_OPTION_HERE, command); return 1; } return 0; } + + +Comp_creator *comp_eq_creator(bool invert) +{ + return invert?(Comp_creator *)&ne_creator:(Comp_creator *)&eq_creator; +} + + +Comp_creator *comp_ge_creator(bool invert) +{ + return invert?(Comp_creator *)<_creator:(Comp_creator *)&ge_creator; +} + + +Comp_creator *comp_gt_creator(bool invert) +{ + return invert?(Comp_creator *)&le_creator:(Comp_creator *)>_creator; +} + + +Comp_creator *comp_le_creator(bool invert) +{ + return invert?(Comp_creator *)>_creator:(Comp_creator *)&le_creator; +} + + +Comp_creator *comp_lt_creator(bool invert) +{ + return invert?(Comp_creator *)&ge_creator:(Comp_creator *)<_creator; +} + + +Comp_creator *comp_ne_creator(bool invert) +{ + return invert?(Comp_creator *)&eq_creator:(Comp_creator *)&ne_creator; +} + + +/* + Construct ALL/ANY/SOME subquery Item + + SYNOPSIS + all_any_subquery_creator() + left_expr - pointer to left expression + cmp - compare function creator + all - true if we create ALL subquery + select_lex - pointer on parsed subquery structure + + RETURN VALUE + constructed Item (or 0 if out of memory) +*/ +Item * all_any_subquery_creator(Item *left_expr, + chooser_compare_func_creator cmp, + bool all, + SELECT_LEX *select_lex) +{ + if ((cmp == &comp_eq_creator) && !all) // = ANY <=> IN + return new Item_in_subselect(left_expr, select_lex); + + if ((cmp == &comp_ne_creator) && all) // <> ALL <=> NOT IN + return new Item_func_not(new Item_in_subselect(left_expr, select_lex)); + + Item_allany_subselect *it= + new Item_allany_subselect(left_expr, (*cmp)(all), select_lex, all); + if (all) + return it->upper_not= new Item_func_not_all(it); /* ALL */ + + return it; /* ANY/SOME */ +} + + +/* + CREATE INDEX and DROP INDEX are implemented by calling ALTER TABLE with + the proper arguments. This isn't very fast but it should work for most + cases. + + In the future ALTER TABLE will notice that only added indexes + and create these one by one for the existing table without having to do + a full rebuild. + + One should normally create all indexes with CREATE TABLE or ALTER TABLE. +*/ + +int mysql_create_index(THD *thd, TABLE_LIST *table_list, List<Key> &keys) +{ + List<create_field> fields; + List<Alter_drop> drop; + List<Alter_column> alter; + HA_CREATE_INFO create_info; + DBUG_ENTER("mysql_create_index"); + bzero((char*) &create_info,sizeof(create_info)); + create_info.db_type=DB_TYPE_DEFAULT; + create_info.default_table_charset= thd->variables.collation_database; + DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, + &create_info, table_list, + fields, keys, drop, alter, 0, (ORDER*)0, + ALTER_ADD_INDEX, DUP_ERROR)); +} + + +int mysql_drop_index(THD *thd, TABLE_LIST *table_list, List<Alter_drop> &drop) +{ + List<create_field> fields; + List<Key> keys; + List<Alter_column> alter; + HA_CREATE_INFO create_info; + DBUG_ENTER("mysql_drop_index"); + bzero((char*) &create_info,sizeof(create_info)); + create_info.db_type=DB_TYPE_DEFAULT; + create_info.default_table_charset= thd->variables.collation_database; + DBUG_RETURN(mysql_alter_table(thd,table_list->db,table_list->real_name, + &create_info, table_list, + fields, keys, drop, alter, 0, (ORDER*)0, + ALTER_DROP_INDEX, DUP_ERROR)); +} + + +/* + Multi update query pre-check + + SYNOPSIS + multi_update_precheck() + thd Thread handler + tables Global table list + + RETURN VALUE + 0 OK + 1 Error (message is sent to user) + -1 Error (message is not sent to user) +*/ + +int multi_update_precheck(THD *thd, TABLE_LIST *tables) +{ + DBUG_ENTER("multi_update_precheck"); + const char *msg= 0; + TABLE_LIST *table; + LEX *lex= thd->lex; + SELECT_LEX *select_lex= &lex->select_lex; + TABLE_LIST *update_list= (TABLE_LIST*)select_lex->table_list.first; + + if (select_lex->item_list.elements != lex->value_list.elements) + { + my_error(ER_WRONG_VALUE_COUNT, MYF(0)); + DBUG_RETURN(-1); + } + /* + Ensure that we have UPDATE or SELECT privilege for each table + The exact privilege is checked in mysql_multi_update() + */ + for (table= update_list; table; table= table->next) + { + if ((check_access(thd, UPDATE_ACL, table->db, + &table->grant.privilege, 0, 1) || + grant_option && check_grant(thd, UPDATE_ACL, table, 0, 1, 1)) && + (check_access(thd, SELECT_ACL, table->db, + &table->grant.privilege, 0, 0) || + grant_option && check_grant(thd, SELECT_ACL, table, 0, 1, 0))) + DBUG_RETURN(1); + + /* + We assign following flag only to copy of table, because it will + be checked only if query contains subqueries i.e. only if copy exists + */ + if (table->table_list) + table->table_list->table_in_update_from_clause= 1; + } + /* + Is there tables of subqueries? + */ + if (&lex->select_lex != lex->all_selects_list) + { + for (table= tables; table; table= table->next) + { + if (table->table_in_update_from_clause) + { + /* + If we check table by local TABLE_LIST copy then we should copy + grants to global table list, because it will be used for table + opening. + */ + if (table->table_list) + table->grant= table->table_list->grant; + } + else + { + if (check_access(thd, SELECT_ACL, table->db, + &table->grant.privilege, 0, 0) || + grant_option && check_grant(thd, SELECT_ACL, table, 0, 1, 0)) + DBUG_RETURN(1); + } + } + } + + if (select_lex->order_list.elements) + msg= "ORDER BY"; + else if (select_lex->select_limit && select_lex->select_limit != + HA_POS_ERROR) + msg= "LIMIT"; + if (msg) + { + my_error(ER_WRONG_USAGE, MYF(0), "UPDATE", msg); + DBUG_RETURN(-1); + } + DBUG_RETURN(0); +} + +/* + Multi delete query pre-check + + SYNOPSIS + multi_delete_precheck() + thd Thread handler + tables Global table list + table_count Pointer to table counter + + RETURN VALUE + 0 OK + 1 error (message is sent to user) + -1 error (message is not sent to user) +*/ +int multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) +{ + DBUG_ENTER("multi_delete_precheck"); + SELECT_LEX *select_lex= &thd->lex->select_lex; + TABLE_LIST *aux_tables= + (TABLE_LIST *)thd->lex->auxilliary_table_list.first; + TABLE_LIST *delete_tables= (TABLE_LIST *)select_lex->table_list.first; + TABLE_LIST *target_tbl; + + *table_count= 0; + + /* sql_yacc guarantees that tables and aux_tables are not zero */ + DBUG_ASSERT(aux_tables != 0); + if (check_db_used(thd, tables) || check_db_used(thd,aux_tables) || + check_table_access(thd,SELECT_ACL, tables,0) || + check_table_access(thd,DELETE_ACL, aux_tables,0)) + DBUG_RETURN(1); + if ((thd->options & OPTION_SAFE_UPDATES) && !select_lex->where) + { + my_error(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, MYF(0)); + DBUG_RETURN(-1); + } + for (target_tbl= aux_tables; target_tbl; target_tbl= target_tbl->next) + { + (*table_count)++; + /* All tables in aux_tables must be found in FROM PART */ + TABLE_LIST *walk; + for (walk= delete_tables; walk; walk= walk->next) + { + if (!my_strcasecmp(table_alias_charset, + target_tbl->alias, walk->alias) && + !strcmp(walk->db, target_tbl->db)) + break; + } + if (!walk) + { + my_error(ER_UNKNOWN_TABLE, MYF(0), target_tbl->real_name, + "MULTI DELETE"); + DBUG_RETURN(-1); + } + if (walk->derived) + { + my_error(ER_NON_UPDATABLE_TABLE, MYF(0), target_tbl->real_name, + "DELETE"); + DBUG_RETURN(-1); + } + walk->lock_type= target_tbl->lock_type; + target_tbl->table_list= walk; // Remember corresponding table + } + DBUG_RETURN(0); +} + + +/* + INSERT ... SELECT query pre-check + + SYNOPSIS + multi_delete_precheck() + thd Thread handler + tables Global table list + + RETURN VALUE + 0 OK + 1 Error (message is sent to user) + -1 Error (message is not sent to user) +*/ + +int insert_select_precheck(THD *thd, TABLE_LIST *tables) +{ + DBUG_ENTER("insert_select_precheck"); + /* + Check that we have modify privileges for the first table and + select privileges for the rest + */ + ulong privilege= (thd->lex->duplicates == DUP_REPLACE ? + INSERT_ACL | DELETE_ACL : INSERT_ACL); + DBUG_RETURN(check_one_table_access(thd, privilege, tables) ? 1 : 0); +} + + +/* + simple UPDATE query pre-check + + SYNOPSIS + update_precheck() + thd Thread handler + tables Global table list + + RETURN VALUE + 0 OK + 1 Error (message is sent to user) + -1 Error (message is not sent to user) +*/ + +int update_precheck(THD *thd, TABLE_LIST *tables) +{ + DBUG_ENTER("update_precheck"); + if (thd->lex->select_lex.item_list.elements != thd->lex->value_list.elements) + { + my_error(ER_WRONG_VALUE_COUNT, MYF(0)); + DBUG_RETURN(-1); + } + DBUG_RETURN((check_db_used(thd, tables) || + check_one_table_access(thd, UPDATE_ACL, tables)) ? 1 : 0); +} + + +/* + simple DELETE query pre-check + + SYNOPSIS + delete_precheck() + thd Thread handler + tables Global table list + + RETURN VALUE + 0 OK + 1 error (message is sent to user) + -1 error (message is not sent to user) +*/ + +int delete_precheck(THD *thd, TABLE_LIST *tables) +{ + DBUG_ENTER("delete_precheck"); + if (check_one_table_access(thd, DELETE_ACL, tables)) + DBUG_RETURN(1); + /* Set privilege for the WHERE clause */ + tables->grant.want_privilege=(SELECT_ACL & ~tables->grant.privilege); + DBUG_RETURN(0); +} + + +/* + simple INSERT query pre-check + + SYNOPSIS + insert_precheck() + thd Thread handler + tables Global table list + + RETURN VALUE + 0 OK + 1 error (message is sent to user) + -1 error (message is not sent to user) +*/ + +int insert_precheck(THD *thd, TABLE_LIST *tables, bool update) +{ + LEX *lex= thd->lex; + DBUG_ENTER("insert_precheck"); + + ulong privilege= (lex->duplicates == DUP_REPLACE ? + INSERT_ACL | DELETE_ACL : INSERT_ACL | update); + + if (check_one_table_access(thd, privilege, tables)) + DBUG_RETURN(1); + + if (lex->select_lex.item_list.elements != lex->value_list.elements) + { + my_error(ER_WRONG_VALUE_COUNT, MYF(0)); + DBUG_RETURN(-1); + } + DBUG_RETURN(0); +} + + +/* + CREATE TABLE query pre-check + + SYNOPSIS + create_table_precheck() + thd Thread handler + tables Global table list + create_table Table which will be created + + RETURN VALUE + 0 OK + 1 Error (message is sent to user) + -1 Error (message is not sent to user) +*/ + +int create_table_precheck(THD *thd, TABLE_LIST *tables, + TABLE_LIST *create_table) +{ + LEX *lex= thd->lex; + DBUG_ENTER("create_table_precheck"); + ulong want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? + CREATE_TMP_ACL : CREATE_ACL); + lex->create_info.alias= create_table->alias; + if (check_access(thd, want_priv, create_table->db, + &create_table->grant.privilege, 0, 0) || + check_merge_table_access(thd, create_table->db, + (TABLE_LIST *) + lex->create_info.merge_list.first)) + DBUG_RETURN(1); + DBUG_RETURN((grant_option && want_priv != CREATE_TMP_ACL && + check_grant(thd, want_priv, create_table, 0, UINT_MAX, 0)) ? + 1 : 0); +} |