diff options
author | unknown <monty@mysql.com> | 2004-10-29 19:26:52 +0300 |
---|---|---|
committer | unknown <monty@mysql.com> | 2004-10-29 19:26:52 +0300 |
commit | f095274fe8c3d3394d6c0ce0a68f4bea04311999 (patch) | |
tree | 23bcc9a71fe7237887a111b158e30f5a6bb665d3 /sql | |
parent | f41bba8c6156a7adf4c67dfa75e16112767a5d3c (diff) | |
parent | 5be6c328f5a9f78f37176bbbd88a538fa3b65fe9 (diff) | |
download | mariadb-git-f095274fe8c3d3394d6c0ce0a68f4bea04311999.tar.gz |
merge with 4.1
BitKeeper/etc/ignore:
auto-union
BitKeeper/etc/logging_ok:
auto-union
BitKeeper/triggers/post-commit:
Auto merged
Docs/Support/texi2html:
Auto merged
Makefile.am:
Auto merged
client/Makefile.am:
Auto merged
client/mysql.cc:
Auto merged
client/mysqldump.c:
Auto merged
include/my_base.h:
Auto merged
include/my_global.h:
Auto merged
include/my_pthread.h:
Auto merged
include/my_sys.h:
Auto merged
include/my_time.h:
Auto merged
include/mysql.h:
Auto merged
include/mysql_com.h:
Auto merged
innobase/buf/buf0buf.c:
Auto merged
innobase/include/row0mysql.h:
Auto merged
innobase/row/row0sel.c:
Auto merged
libmysql/libmysql.c:
Auto merged
libmysqld/examples/Makefile.am:
Auto merged
myisam/mi_check.c:
Auto merged
mysql-test/include/ps_modify.inc:
Auto merged
mysql-test/install_test_db.sh:
Auto merged
mysql-test/r/alter_table.result:
Auto merged
mysql-test/r/auto_increment.result:
Auto merged
mysql-test/r/bdb.result:
Auto merged
mysql-test/r/ctype_latin1_de.result:
Auto merged
mysql-test/r/ctype_recoding.result:
Auto merged
mysql-test/r/fulltext.result:
Auto merged
mysql-test/r/func_gconcat.result:
Auto merged
mysql-test/r/func_group.result:
Auto merged
mysql-test/r/func_if.result:
Auto merged
mysql-test/t/derived.test:
Auto merged
mysql-test/t/insert.test:
merge with 4.1
Fixed test case to not use 'if exists' when it shouldn't
mysql-test/t/range.test:
merge with 4.1
Added missing drop table
sql/ha_ndbcluster.cc:
merge with 4.1
Simple optimization: use max() instead of ? :
sql/item_func.cc:
merge with 4.1
(Added back old variable names for easier merges)
sql/opt_range.cc:
merge with 4.1
Removed argument 'parent_alloc' from QUICK_RANGE_SELECT as this was not used
Added assert if using QUICK_GROUP_MIN_MAX_SELECT with parent_alloc as the init() function can't handle this
Changed back get_quick_select_for_ref() to use it's own alloc root becasue this function may be called several times for one query
sql/sql_handler.cc:
merge with 4.1
change variable 'err' to 'error' as same function had a label named 'err'
sql/sql_update.cc:
Use multi-update code from 5.0 instead of 4.1
We will fix the locking code shortly in 5.0 to be faster than in 4.1
Diffstat (limited to 'sql')
123 files changed, 6226 insertions, 2779 deletions
diff --git a/sql/Makefile.am b/sql/Makefile.am index 2c99d42f3ee..a3935835a2d 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -37,7 +37,7 @@ LDADD = @isam_libs@ \ $(top_builddir)/mysys/libmysys.a \ $(top_builddir)/dbug/libdbug.a \ $(top_builddir)/regex/libregex.a \ - $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ + $(top_builddir)/strings/libmystrings.a @ZLIB_LIBS@ @NDB_SCI_LIBS@ mysqld_LDADD = @MYSQLD_EXTRA_LDFLAGS@ \ @bdb_libs@ @innodb_libs@ @pstack_libs@ \ diff --git a/sql/discover.cc b/sql/discover.cc index 696be193148..1251055c70e 100644 --- a/sql/discover.cc +++ b/sql/discover.cc @@ -125,47 +125,4 @@ int writefrm(const char *name, const void *frmdata, uint len) -/* - Try to discover table from handler and - if found, write the frm file to disk. - - RETURN VALUES: - 0 : Table existed in handler and created - on disk if so requested - 1 : Table does not exist - >1 : error - -*/ -int create_table_from_handler(const char *db, - const char *name, - bool create_if_found) -{ - int error= 0; - const void* frmblob = NULL; - char path[FN_REFLEN]; - uint frmlen = 0; - DBUG_ENTER("create_table_from_handler"); - DBUG_PRINT("enter", ("create_if_found: %d", create_if_found)); - - if (ha_discover(db, name, &frmblob, &frmlen)) - DBUG_RETURN(1); // Table does not exist - - // Table exists in handler - if (create_if_found) - { - (void)strxnmov(path,FN_REFLEN,mysql_data_home,"/",db,"/",name,NullS); - // Save the frm file - error = writefrm(path, frmblob, frmlen); - } - - if (frmblob) - my_free((char*) frmblob,MYF(0)); - DBUG_RETURN(error); -} - -int table_exists_in_handler(const char *db, - const char *name) -{ - return (create_table_from_handler(db, name, false) == 0); -} diff --git a/sql/examples/ha_archive.cc b/sql/examples/ha_archive.cc index c004330932c..6fbfb3f9f9d 100644 --- a/sql/examples/ha_archive.cc +++ b/sql/examples/ha_archive.cc @@ -42,7 +42,18 @@ handle bulk inserts as well (that is if someone was trying to read at the same time since we would want to flush). - No attempts at durability are made. You can corrupt your data. + A "meta" file is kept. All this file does is contain information on + the number of rows. + + No attempts at durability are made. You can corrupt your data. A repair + method was added to repair the meta file that stores row information, + but if your data file gets corrupted I haven't solved that. I could + create a repair that would solve this, but do you want to take a + chance of loosing your data? + + Locks are row level, and you will get a consistant read. Transactions + will be added later (they are not that hard to add at this + stage). For performance as far as table scans go it is quite fast. I don't have good numbers but locally it has out performed both Innodb and MyISAM. For @@ -70,16 +81,37 @@ Allow users to set compression level. Add truncate table command. Implement versioning, should be easy. - Implement optimize so we can fix broken tables. Allow for errors, find a way to mark bad rows. - See if during an optimize you can make the table smaller. Talk to the gzip guys, come up with a writable format so that updates are doable without switching to a block method. Add optional feature so that rows can be flushed at interval (which will cause less - compression but may speed up ordered searches). + compression but may speed up ordered searches). + Checkpoint the meta file to allow for faster rebuilds. + Dirty open (right now the meta file is repaired if a crash occured). + Transactions. + Option to allow for dirty reads, this would lower the sync calls, which would make + inserts a lot faster, but would mean highly arbitrary reads. -Brian */ +/* + Notes on file formats. + The Meta file is layed out as: + check - Just an int of 254 to make sure that the the file we are opening was + never corrupted. + version - The current version of the file format. + rows - This is an unsigned long long which is the number of rows in the data + file. + check point - Reserved for future use + dirty - Status of the file, whether or not its values are the latest. This + flag is what causes a repair to occur + + The data file: + check - Just an int of 254 to make sure that the the file we are opening was + never corrupted. + version - The current version of the file format. + data - The data is stored in a "row +blobs" format. +*/ /* Variables for archive share methods */ pthread_mutex_t archive_mutex; @@ -87,7 +119,18 @@ static HASH archive_open_tables; static int archive_init= 0; /* The file extension */ -#define ARZ ".ARZ" +#define ARZ ".ARZ" // The data file +#define ARN ".ARN" // Files used during an optimize call +#define ARM ".ARM" // Meta file +/* + uchar + uchar + ulonglong + ulonglong + uchar +*/ +#define META_BUFFER_SIZE 19 // Size of the data used in the meta file +/* + uchar + uchar +*/ +#define DATA_BUFFER_SIZE 2 // Size of the data used in the data file +#define ARCHIVE_CHECK_HEADER 254 // The number we use to determine corruption /* Used for hash table that tracks open tables. @@ -99,14 +142,130 @@ static byte* archive_get_key(ARCHIVE_SHARE *share,uint *length, return (byte*) share->table_name; } +/* + This method reads the header of a datafile and returns whether or not it was successful. +*/ +int ha_archive::read_data_header(gzFile file_to_read) +{ + uchar data_buffer[DATA_BUFFER_SIZE]; + DBUG_ENTER("ha_archive::read_data_header"); + + if (gzrewind(file_to_read) == -1) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); + + if (gzread(file_to_read, data_buffer, DATA_BUFFER_SIZE) != DATA_BUFFER_SIZE) + DBUG_RETURN(errno ? errno : -1); + + DBUG_PRINT("ha_archive::read_data_header", ("Check %u", data_buffer[0])); + DBUG_PRINT("ha_archive::read_data_header", ("Version %u", data_buffer[1])); + + if ((data_buffer[0] != (uchar)ARCHIVE_CHECK_HEADER) && + (data_buffer[1] != (uchar)ARCHIVE_VERSION)) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); + + DBUG_RETURN(0); +} /* - Example of simple lock controls. - See ha_example.cc for a description. + This method writes out the header of a datafile and returns whether or not it was successful. */ -static ARCHIVE_SHARE *get_share(const char *table_name, TABLE *table) +int ha_archive::write_data_header(gzFile file_to_write) +{ + uchar data_buffer[DATA_BUFFER_SIZE]; + DBUG_ENTER("ha_archive::write_data_header"); + + data_buffer[0]= (uchar)ARCHIVE_CHECK_HEADER; + data_buffer[1]= (uchar)ARCHIVE_VERSION; + + if (gzwrite(file_to_write, &data_buffer, DATA_BUFFER_SIZE) != + sizeof(DATA_BUFFER_SIZE)) + goto error; + DBUG_PRINT("ha_archive::write_data_header", ("Check %u", (uint)data_buffer[0])); + DBUG_PRINT("ha_archive::write_data_header", ("Version %u", (uint)data_buffer[1])); + + DBUG_RETURN(0); +error: + DBUG_RETURN(errno); +} + +/* + This method reads the header of a meta file and returns whether or not it was successful. + *rows will contain the current number of rows in the data file upon success. +*/ +int ha_archive::read_meta_file(File meta_file, ulonglong *rows) +{ + uchar meta_buffer[META_BUFFER_SIZE]; + ulonglong check_point; + + DBUG_ENTER("ha_archive::read_meta_file"); + + VOID(my_seek(meta_file, 0, MY_SEEK_SET, MYF(0))); + if (my_read(meta_file, (byte*)meta_buffer, META_BUFFER_SIZE, 0) != META_BUFFER_SIZE) + DBUG_RETURN(-1); + + /* + Parse out the meta data, we ignore version at the moment + */ + *rows= uint8korr(meta_buffer + 2); + check_point= uint8korr(meta_buffer + 10); + + DBUG_PRINT("ha_archive::read_meta_file", ("Check %d", (uint)meta_buffer[0])); + DBUG_PRINT("ha_archive::read_meta_file", ("Version %d", (uint)meta_buffer[1])); + DBUG_PRINT("ha_archive::read_meta_file", ("Rows %lld", *rows)); + DBUG_PRINT("ha_archive::read_meta_file", ("Checkpoint %lld", check_point)); + DBUG_PRINT("ha_archive::read_meta_file", ("Dirty %d", (int)meta_buffer[18])); + + if ((meta_buffer[0] != (uchar)ARCHIVE_CHECK_HEADER) || + ((bool)meta_buffer[18] == TRUE)) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); + + my_sync(meta_file, MYF(MY_WME)); + + DBUG_RETURN(0); +} + +/* + This method writes out the header of a meta file and returns whether or not it was successful. + By setting dirty you say whether or not the file represents the actual state of the data file. + Upon ::open() we set to dirty, and upon ::close() we set to clean. If we determine during + a read that the file was dirty we will force a rebuild of this file. +*/ +int ha_archive::write_meta_file(File meta_file, ulonglong rows, bool dirty) +{ + uchar meta_buffer[META_BUFFER_SIZE]; + ulonglong check_point= 0; //Reserved for the future + + DBUG_ENTER("ha_archive::write_meta_file"); + + meta_buffer[0]= (uchar)ARCHIVE_CHECK_HEADER; + meta_buffer[1]= (uchar)ARCHIVE_VERSION; + int8store(meta_buffer + 2, rows); + int8store(meta_buffer + 10, check_point); + *(meta_buffer + 18)= (uchar)dirty; + DBUG_PRINT("ha_archive::write_meta_file", ("Check %d", (uint)ARCHIVE_CHECK_HEADER)); + DBUG_PRINT("ha_archive::write_meta_file", ("Version %d", (uint)ARCHIVE_VERSION)); + DBUG_PRINT("ha_archive::write_meta_file", ("Rows %llu", rows)); + DBUG_PRINT("ha_archive::write_meta_file", ("Checkpoint %llu", check_point)); + DBUG_PRINT("ha_archive::write_meta_file", ("Dirty %d", (uint)dirty)); + + VOID(my_seek(meta_file, 0, MY_SEEK_SET, MYF(0))); + if (my_write(meta_file, (byte *)meta_buffer, META_BUFFER_SIZE, 0) != META_BUFFER_SIZE) + DBUG_RETURN(-1); + + my_sync(meta_file, MYF(MY_WME)); + + DBUG_RETURN(0); +} + + +/* + We create the shared memory space that we will use for the open table. + See ha_example.cc for a longer description. +*/ +ARCHIVE_SHARE *ha_archive::get_share(const char *table_name, TABLE *table) { ARCHIVE_SHARE *share; + char meta_file_name[FN_REFLEN]; uint length; char *tmp_name; @@ -117,7 +276,7 @@ static ARCHIVE_SHARE *get_share(const char *table_name, TABLE *table) if (!archive_init) { VOID(pthread_mutex_init(&archive_mutex,MY_MUTEX_INIT_FAST)); - if (!hash_init(&archive_open_tables,system_charset_info,32,0,0, + if (hash_init(&archive_open_tables,system_charset_info,32,0,0, (hash_get_key) archive_get_key,0,0)) { pthread_mutex_unlock(&LOCK_mysql_create_db); @@ -143,33 +302,62 @@ static ARCHIVE_SHARE *get_share(const char *table_name, TABLE *table) return NULL; } - share->use_count=0; - share->table_name_length=length; - share->table_name=tmp_name; + share->use_count= 0; + share->table_name_length= length; + share->table_name= tmp_name; fn_format(share->data_file_name,table_name,"",ARZ,MY_REPLACE_EXT|MY_UNPACK_FILENAME); + fn_format(meta_file_name,table_name,"",ARM,MY_REPLACE_EXT|MY_UNPACK_FILENAME); strmov(share->table_name,table_name); + /* + We will use this lock for rows. + */ + VOID(pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST)); + if ((share->meta_file= my_open(meta_file_name, O_RDWR, MYF(0))) == -1) + goto error; + + if (read_meta_file(share->meta_file, &share->rows_recorded)) + { + /* + The problem here is that for some reason, probably a crash, the meta + file has been corrupted. So what do we do? Well we try to rebuild it + ourself. Once that happens, we reread it, but if that fails we just + call it quits and return an error. + */ + if (rebuild_meta_file(share->table_name, share->meta_file)) + goto error; + if (read_meta_file(share->meta_file, &share->rows_recorded)) + goto error; + } + /* + After we read, we set the file to dirty. When we close, we will do the + opposite. + */ + (void)write_meta_file(share->meta_file, share->rows_recorded, TRUE); /* It is expensive to open and close the data files and since you can't have a gzip file that can be both read and written we keep a writer open that is shared amoung all open tables. */ if ((share->archive_write= gzopen(share->data_file_name, "ab")) == NULL) - goto error; + goto error2; if (my_hash_insert(&archive_open_tables, (byte*) share)) - goto error; + goto error2; thr_lock_init(&share->lock); if (pthread_mutex_init(&share->mutex,MY_MUTEX_INIT_FAST)) - goto error2; + goto error3; } share->use_count++; pthread_mutex_unlock(&archive_mutex); return share; -error2: +error3: + VOID(pthread_mutex_destroy(&share->mutex)); thr_lock_delete(&share->lock); /* We close, but ignore errors since we already have errors */ (void)gzclose(share->archive_write); +error2: + my_close(share->meta_file,MYF(0)); error: pthread_mutex_unlock(&archive_mutex); my_free((gptr) share, MYF(0)); @@ -179,10 +367,10 @@ error: /* - Free lock controls. + Free the share. See ha_example.cc for a description. */ -static int free_share(ARCHIVE_SHARE *share) +int ha_archive::free_share(ARCHIVE_SHARE *share) { int rc= 0; pthread_mutex_lock(&archive_mutex); @@ -190,7 +378,8 @@ static int free_share(ARCHIVE_SHARE *share) { hash_delete(&archive_open_tables, (byte*) share); thr_lock_delete(&share->lock); - pthread_mutex_destroy(&share->mutex); + VOID(pthread_mutex_destroy(&share->mutex)); + (void)write_meta_file(share->meta_file, share->rows_recorded, FALSE); if (gzclose(share->archive_write) == Z_ERRNO) rc= 1; my_free((gptr) share, MYF(0)); @@ -205,7 +394,7 @@ static int free_share(ARCHIVE_SHARE *share) We just implement one additional file extension. */ const char **ha_archive::bas_ext() const -{ static const char *ext[]= { ARZ, NullS }; return ext; } +{ static const char *ext[]= { ARZ, ARN, ARM, NullS }; return ext; } /* @@ -213,7 +402,6 @@ const char **ha_archive::bas_ext() const Create/get our shared structure. Init out lock. We open the file we will read from. - Set the size of ref_length. */ int ha_archive::open(const char *name, int mode, uint test_if_locked) { @@ -266,54 +454,63 @@ int ha_archive::close(void) /* - We create our data file here. The format is pretty simple. The first - bytes in any file are the version number. Currently we do nothing - with this, but in the future this gives us the ability to figure out - version if we change the format at all. After the version we - starting writing our rows. Unlike other storage engines we do not - "pack" our data. Since we are about to do a general compression, - packing would just be a waste of CPU time. If the table has blobs - they are written after the row in the order of creation. - - So to read a row we: - Read the version - Read the record and copy it into buf - Loop through any blobs and read them + We create our data file here. The format is pretty simple. + You can read about the format of the data file above. + Unlike other storage engines we do not "pack" our data. Since we + are about to do a general compression, packing would just be a waste of + CPU time. If the table has blobs they are written after the row in the order + of creation. */ int ha_archive::create(const char *name, TABLE *table_arg, HA_CREATE_INFO *create_info) { - File create_file; + File create_file; // We use to create the datafile and the metafile char name_buff[FN_REFLEN]; - size_t written; int error; DBUG_ENTER("ha_archive::create"); + if ((create_file= my_create(fn_format(name_buff,name,"",ARM, + MY_REPLACE_EXT|MY_UNPACK_FILENAME),0, + O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) + { + error= my_errno; + goto error; + } + write_meta_file(create_file, 0, FALSE); + my_close(create_file,MYF(0)); + + /* + We reuse name_buff since it is available. + */ if ((create_file= my_create(fn_format(name_buff,name,"",ARZ, MY_REPLACE_EXT|MY_UNPACK_FILENAME),0, O_RDWR | O_TRUNC,MYF(MY_WME))) < 0) { error= my_errno; - goto err; + goto error; } if ((archive= gzdopen(create_file, "ab")) == NULL) { error= errno; delete_table(name); - goto err; + goto error; } - version= ARCHIVE_VERSION; - written= gzwrite(archive, &version, sizeof(version)); - if (gzclose(archive) || written != sizeof(version)) + if (write_data_header(archive)) { - error= errno; - delete_table(name); - goto err; + gzclose(archive); + goto error2; } + + if (gzclose(archive)) + goto error2; + DBUG_RETURN(0); -err: +error2: + error= errno; + delete_table(name); +error: /* Return error number, if we got one */ DBUG_RETURN(error ? error : -1); } @@ -322,33 +519,50 @@ err: /* Look at ha_archive::open() for an explanation of the row format. Here we just write out the row. + + Wondering about start_bulk_insert()? We don't implement it for + archive since it optimizes for lots of writes. The only save + for implementing start_bulk_insert() is that we could skip + setting dirty to true each time. */ int ha_archive::write_row(byte * buf) { - char *pos; z_off_t written; DBUG_ENTER("ha_archive::write_row"); statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); + pthread_mutex_lock(&share->mutex); written= gzwrite(share->archive_write, buf, table->reclength); + DBUG_PRINT("ha_archive::get_row", ("Wrote %d bytes expected %d", written, table->reclength)); share->dirty= TRUE; if (written != table->reclength) - DBUG_RETURN(errno ? errno : -1); - + goto error; + /* + We should probably mark the table as damagaged if the record is written + but the blob fails. + */ for (Field_blob **field=table->blob_field ; *field ; field++) { char *ptr; uint32 size= (*field)->get_length(); - (*field)->get_ptr(&ptr); - written= gzwrite(share->archive_write, ptr, (unsigned)size); - if (written != size) - DBUG_RETURN(errno ? errno : -1); + if (size) + { + (*field)->get_ptr(&ptr); + written= gzwrite(share->archive_write, ptr, (unsigned)size); + if (written != size) + goto error; + } } + share->rows_recorded++; + pthread_mutex_unlock(&share->mutex); DBUG_RETURN(0); +error: + pthread_mutex_unlock(&share->mutex); + DBUG_RETURN(errno ? errno : -1); } @@ -364,47 +578,28 @@ int ha_archive::rnd_init(bool scan) int read; // gzread() returns int, and we use this to check the header /* We rewind the file so that we can read from the beginning if scan */ - if(scan) + if (scan) { + scan_rows= share->rows_recorded; records= 0; - if (gzrewind(archive)) - DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); - } - /* - If dirty, we lock, and then reset/flush the data. - I found that just calling gzflush() doesn't always work. - */ - if (share->dirty == TRUE) - { - pthread_mutex_lock(&share->mutex); + /* + If dirty, we lock, and then reset/flush the data. + I found that just calling gzflush() doesn't always work. + */ if (share->dirty == TRUE) { -/* I was having problems with OSX, but it worked for 10.3 so I am wrapping this with and ifdef */ -#ifdef BROKEN_GZFLUSH - gzclose(share->archive_write); - if ((share->archive_write= gzopen(share->data_file_name, "ab")) == NULL) + pthread_mutex_lock(&share->mutex); + if (share->dirty == TRUE) { - pthread_mutex_unlock(&share->mutex); - DBUG_RETURN(errno ? errno : -1); + gzflush(share->archive_write, Z_SYNC_FLUSH); + share->dirty= FALSE; } -#else - gzflush(share->archive_write, Z_SYNC_FLUSH); -#endif - share->dirty= FALSE; + pthread_mutex_unlock(&share->mutex); } - pthread_mutex_unlock(&share->mutex); - } - - /* - At the moment we just check the size of version to make sure the header is - intact. - */ - if (scan) - { - read= gzread(archive, &version, sizeof(version)); - if (read != sizeof(version)) - DBUG_RETURN(errno ? errno : -1); + + if (read_data_header(archive)) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); } DBUG_RETURN(0); @@ -415,14 +610,19 @@ int ha_archive::rnd_init(bool scan) This is the method that is used to read a row. It assumes that the row is positioned where you want it. */ -int ha_archive::get_row(byte *buf) +int ha_archive::get_row(gzFile file_to_read, byte *buf) { int read; // Bytes read, gzread() returns int char *last; size_t total_blob_length= 0; + Field_blob **field; DBUG_ENTER("ha_archive::get_row"); - read= gzread(archive, buf, table->reclength); + read= gzread(file_to_read, buf, table->reclength); + DBUG_PRINT("ha_archive::get_row", ("Read %d bytes expected %d", read, table->reclength)); + + if (read == Z_STREAM_ERROR) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); /* If we read nothing we are at the end of the file */ if (read == 0) @@ -433,7 +633,7 @@ int ha_archive::get_row(byte *buf) DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); /* Calculate blob length, we use this for our buffer */ - for (Field_blob **field=table->blob_field; *field ; field++) + for (field=table->blob_field; *field ; field++) total_blob_length += (*field)->get_length(); /* Adjust our row buffer if we need be */ @@ -441,14 +641,17 @@ int ha_archive::get_row(byte *buf) last= (char *)buffer.ptr(); /* Loop through our blobs and read them */ - for (Field_blob **field=table->blob_field; *field ; field++) + for (field=table->blob_field; *field ; field++) { size_t size= (*field)->get_length(); - read= gzread(archive, last, size); - if ((size_t) read != size) - DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); - (*field)->set_ptr(size, last); - last += size; + if (size) + { + read= gzread(file_to_read, last, size); + if ((size_t) read != size) + DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); + (*field)->set_ptr(size, last); + last += size; + } } DBUG_RETURN(0); } @@ -464,9 +667,15 @@ int ha_archive::rnd_next(byte *buf) int rc; DBUG_ENTER("ha_archive::rnd_next"); + if (!scan_rows) + DBUG_RETURN(HA_ERR_END_OF_FILE); + scan_rows--; + statistic_increment(ha_read_rnd_next_count,&LOCK_status); current_position= gztell(archive); - rc= get_row(buf); + rc= get_row(archive, buf); + + if (rc != HA_ERR_END_OF_FILE) records++; @@ -479,6 +688,7 @@ int ha_archive::rnd_next(byte *buf) each call to ha_archive::rnd_next() if an ordering of the rows is needed. */ + void ha_archive::position(const byte *record) { DBUG_ENTER("ha_archive::position"); @@ -501,9 +711,166 @@ int ha_archive::rnd_pos(byte * buf, byte *pos) current_position= ha_get_ptr(pos, ref_length); z_off_t seek= gzseek(archive, current_position, SEEK_SET); - DBUG_RETURN(get_row(buf)); + DBUG_RETURN(get_row(archive, buf)); } +/* + This method rebuilds the meta file. It does this by walking the datafile and + rewriting the meta file. +*/ +int ha_archive::rebuild_meta_file(char *table_name, File meta_file) +{ + int rc; + byte *buf; + ulonglong rows_recorded= 0; + gzFile rebuild_file; /* Archive file we are working with */ + char data_file_name[FN_REFLEN]; + DBUG_ENTER("ha_archive::rebuild_meta_file"); + + /* + Open up the meta file to recreate it. + */ + fn_format(data_file_name, table_name, "", ARZ, + MY_REPLACE_EXT|MY_UNPACK_FILENAME); + if ((rebuild_file= gzopen(data_file_name, "rb")) == NULL) + DBUG_RETURN(errno ? errno : -1); + + if (rc= read_data_header(rebuild_file)) + goto error; + + /* + We malloc up the buffer we will use for counting the rows. + I know, this malloc'ing memory but this should be a very + rare event. + */ + if (!(buf= (byte*) my_malloc(table->rec_buff_length > sizeof(ulonglong) +1 ? + table->rec_buff_length : sizeof(ulonglong) +1 , + MYF(MY_WME)))) + { + rc= HA_ERR_CRASHED_ON_USAGE; + goto error; + } + + while (!(rc= get_row(rebuild_file, buf))) + rows_recorded++; + + /* + Only if we reach the end of the file do we assume we can rewrite. + At this point we reset rc to a non-message state. + */ + if (rc == HA_ERR_END_OF_FILE) + { + (void)write_meta_file(meta_file, rows_recorded, FALSE); + rc= 0; + } + + my_free((gptr) buf, MYF(0)); +error: + gzclose(rebuild_file); + + DBUG_RETURN(rc); +} + +/* + The table can become fragmented if data was inserted, read, and then + inserted again. What we do is open up the file and recompress it completely. +*/ +int ha_archive::optimize(THD* thd, HA_CHECK_OPT* check_opt) +{ + DBUG_ENTER("ha_archive::optimize"); + int read; // Bytes read, gzread() returns int + gzFile reader, writer; + char block[IO_SIZE]; + char writer_filename[FN_REFLEN]; + + /* Lets create a file to contain the new data */ + fn_format(writer_filename, share->table_name, "", ARN, + MY_REPLACE_EXT|MY_UNPACK_FILENAME); + + /* Closing will cause all data waiting to be flushed, to be flushed */ + gzclose(share->archive_write); + + if ((reader= gzopen(share->data_file_name, "rb")) == NULL) + DBUG_RETURN(-1); + + if ((writer= gzopen(writer_filename, "wb")) == NULL) + { + gzclose(reader); + DBUG_RETURN(-1); + } + + while (read= gzread(reader, block, IO_SIZE)) + gzwrite(writer, block, read); + + gzclose(reader); + gzclose(writer); + + my_rename(writer_filename,share->data_file_name,MYF(0)); + + /* + We reopen the file in case some IO is waiting to go through. + In theory the table is closed right after this operation, + but it is possible for IO to still happen. + I may be being a bit too paranoid right here. + */ + if ((share->archive_write= gzopen(share->data_file_name, "ab")) == NULL) + DBUG_RETURN(errno ? errno : -1); + share->dirty= FALSE; + + DBUG_RETURN(0); +} + + +/* + No transactions yet, so this is pretty dull. +*/ +int ha_archive::external_lock(THD *thd, int lock_type) +{ + DBUG_ENTER("ha_archive::external_lock"); + DBUG_RETURN(0); +} + +/* + Below is an example of how to setup row level locking. +*/ +THR_LOCK_DATA **ha_archive::store_lock(THD *thd, + THR_LOCK_DATA **to, + enum thr_lock_type lock_type) +{ + if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) + { + /* + Here is where we get into the guts of a row level lock. + If TL_UNLOCK is set + If we are not doing a LOCK TABLE or DISCARD/IMPORT + TABLESPACE, then allow multiple writers + */ + + if ((lock_type >= TL_WRITE_CONCURRENT_INSERT && + lock_type <= TL_WRITE) && !thd->in_lock_tables + && !thd->tablespace_op) + lock_type = TL_WRITE_ALLOW_WRITE; + + /* + In queries of type INSERT INTO t1 SELECT ... FROM t2 ... + MySQL would use the lock TL_READ_NO_INSERT on t2, and that + would conflict with TL_WRITE_ALLOW_WRITE, blocking all inserts + to t2. Convert the lock to a normal read lock to allow + concurrent inserts to t2. + */ + + if (lock_type == TL_READ_NO_INSERT && !thd->in_lock_tables) + lock_type = TL_READ; + + lock.type=lock_type; + } + + *to++= &lock; + + return to; +} + + /****************************************************************************** Everything below here is default, please look at ha_example.cc for @@ -573,8 +940,8 @@ void ha_archive::info(uint flag) DBUG_ENTER("ha_archive::info"); /* This is a lie, but you don't want the optimizer to see zero or 1 */ - if (records < 2) - records= 2; + records= share->rows_recorded; + deleted= 0; DBUG_VOID_RETURN; } @@ -591,23 +958,6 @@ int ha_archive::reset(void) DBUG_RETURN(0); } - -int ha_archive::external_lock(THD *thd, int lock_type) -{ - DBUG_ENTER("ha_archive::external_lock"); - DBUG_RETURN(0); -} - -THR_LOCK_DATA **ha_archive::store_lock(THD *thd, - THR_LOCK_DATA **to, - enum thr_lock_type lock_type) -{ - if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) - lock.type=lock_type; - *to++= &lock; - return to; -} - ha_rows ha_archive::records_in_range(uint inx, key_range *min_key, key_range *max_key) { diff --git a/sql/examples/ha_archive.h b/sql/examples/ha_archive.h index f08353a5d6c..b619de5f6c1 100644 --- a/sql/examples/ha_archive.h +++ b/sql/examples/ha_archive.h @@ -32,8 +32,10 @@ typedef struct st_archive_share { uint table_name_length,use_count; pthread_mutex_t mutex; THR_LOCK lock; + File meta_file; /* Meta file we use */ gzFile archive_write; /* Archive file we are working with */ bool dirty; /* Flag for if a flush should occur */ + ulonglong rows_recorded; /* Number of rows in tables */ } ARCHIVE_SHARE; /* @@ -50,7 +52,7 @@ class ha_archive: public handler z_off_t current_position; /* The position of the row we just read */ byte byte_buffer[IO_SIZE]; /* Initial buffer for our string */ String buffer; /* Buffer used for blob storage */ - unsigned int version; /* Used for recording version */ + ulonglong scan_rows; /* Number of rows left in scan */ public: ha_archive(TABLE *table): handler(table) @@ -104,7 +106,14 @@ public: int rnd_init(bool scan=1); int rnd_next(byte *buf); int rnd_pos(byte * buf, byte *pos); - int get_row(byte *buf); + int get_row(gzFile file_to_read, byte *buf); + int read_meta_file(File meta_file, ulonglong *rows); + int write_meta_file(File meta_file, ulonglong rows, bool dirty); + ARCHIVE_SHARE *get_share(const char *table_name, TABLE *table); + int free_share(ARCHIVE_SHARE *share); + int rebuild_meta_file(char *table_name, File meta_file); + int read_data_header(gzFile file_to_read); + int write_data_header(gzFile file_to_write); void position(const byte *record); void info(uint); int extra(enum ha_extra_function operation); @@ -112,7 +121,7 @@ public: int external_lock(THD *thd, int lock_type); ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key); int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info); - + int optimize(THD* thd, HA_CHECK_OPT* check_opt); THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type); }; diff --git a/sql/examples/ha_example.cc b/sql/examples/ha_example.cc index 097abd48e05..cb0780ea74d 100644 --- a/sql/examples/ha_example.cc +++ b/sql/examples/ha_example.cc @@ -263,8 +263,8 @@ int ha_example::write_row(byte * buf) clause was used. Consecutive ordering is not guarenteed. Currently new_data will not have an updated auto_increament record, or and updated timestamp field. You can do these for example by doing these: - if (table->timestamp_on_update_now) - update_timestamp(new_row+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); if (table->next_number_field && record == table->record[0]) update_auto_increment(); diff --git a/sql/examples/ha_tina.cc b/sql/examples/ha_tina.cc index 06a19e478ae..0345a170c3c 100644 --- a/sql/examples/ha_tina.cc +++ b/sql/examples/ha_tina.cc @@ -428,8 +428,8 @@ int ha_tina::write_row(byte * buf) statistic_increment(ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); size= encode_quote(buf); @@ -464,8 +464,8 @@ int ha_tina::update_row(const byte * old_data, byte * new_data) statistic_increment(ha_update_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(new_data+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); size= encode_quote(new_data); diff --git a/sql/examples/ha_tina.h b/sql/examples/ha_tina.h index 67a907fddb6..22193c01013 100644 --- a/sql/examples/ha_tina.h +++ b/sql/examples/ha_tina.h @@ -90,6 +90,12 @@ class ha_tina: public handler /* The next method will never be called */ virtual double read_time(ha_rows rows) { DBUG_ASSERT(0); return((double) rows / 20.0+1); } virtual bool fast_key_read() { return 1;} + /* + TODO: return actual upper bound of number of records in the table. + (e.g. save number of records seen on full table scan and/or use file size + as upper bound) + */ + ha_rows estimate_rows_upper_bound() { return HA_POS_ERROR; } int open(const char *name, int mode, uint test_if_locked); int close(void); diff --git a/sql/field.cc b/sql/field.cc index 1cadf186a4c..7a8f59c25fe 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -188,42 +188,51 @@ static bool test_if_real(const char *str,int length, CHARSET_INFO *cs) FIELD_CAST_STOP */ static Field::field_cast_enum field_cast_decimal[]= -{Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, +{Field::FIELD_CAST_DECIMAL, + Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_tiny[]= -{Field::FIELD_CAST_SHORT, Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, +{Field::FIELD_CAST_TINY, + Field::FIELD_CAST_SHORT, Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_FLOAT, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_short[]= -{Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, +{Field::FIELD_CAST_SHORT, + Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_FLOAT, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_medium[]= -{Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, +{Field::FIELD_CAST_MEDIUM, + Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_long[]= -{Field::FIELD_CAST_LONGLONG, +{Field::FIELD_CAST_LONG, + Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_longlong[]= -{Field::FIELD_CAST_DOUBLE, +{Field::FIELD_CAST_LONGLONG, + Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_float[]= -{Field::FIELD_CAST_DOUBLE, +{Field::FIELD_CAST_FLOAT, + Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_double[]= -{Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, +{Field::FIELD_CAST_DOUBLE, + Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_null[]= -{Field::FIELD_CAST_DECIMAL, Field::FIELD_CAST_TINY, Field::FIELD_CAST_SHORT, +{Field::FIELD_CAST_NULL, + Field::FIELD_CAST_DECIMAL, Field::FIELD_CAST_TINY, Field::FIELD_CAST_SHORT, Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_FLOAT, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_TIMESTAMP, Field::FIELD_CAST_YEAR, @@ -234,44 +243,54 @@ static Field::field_cast_enum field_cast_null[]= Field::FIELD_CAST_GEOM, Field::FIELD_CAST_ENUM, Field::FIELD_CAST_SET, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_timestamp[]= -{Field::FIELD_CAST_DATETIME, +{Field::FIELD_CAST_TIMESTAMP, + Field::FIELD_CAST_DATETIME, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_year[]= -{Field::FIELD_CAST_SHORT, Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, +{Field::FIELD_CAST_YEAR, + Field::FIELD_CAST_SHORT, Field::FIELD_CAST_MEDIUM, Field::FIELD_CAST_LONG, Field::FIELD_CAST_LONGLONG, Field::FIELD_CAST_FLOAT, Field::FIELD_CAST_DOUBLE, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_date[]= -{Field::FIELD_CAST_DATETIME, +{Field::FIELD_CAST_DATE, + Field::FIELD_CAST_DATETIME, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_newdate[]= -{Field::FIELD_CAST_DATETIME, +{Field::FIELD_CAST_NEWDATE, + Field::FIELD_CAST_DATETIME, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_time[]= -{Field::FIELD_CAST_DATETIME, +{Field::FIELD_CAST_TIME, + Field::FIELD_CAST_DATETIME, Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_datetime[]= -{Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, +{Field::FIELD_CAST_DATETIME, + Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_string[]= -{Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; +{Field::FIELD_CAST_STRING, + Field::FIELD_CAST_VARSTRING, Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_varstring[]= -{Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; +{Field::FIELD_CAST_VARSTRING, + Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_blob[]= -{Field::FIELD_CAST_STOP}; +{Field::FIELD_CAST_BLOB, + Field::FIELD_CAST_STOP}; +/* + Geometrical, enum and set fields can be casted only to expressions +*/ static Field::field_cast_enum field_cast_geom[]= {Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_enum[]= -{Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, - Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; +{Field::FIELD_CAST_STOP}; static Field::field_cast_enum field_cast_set[]= -{Field::FIELD_CAST_STRING, Field::FIELD_CAST_VARSTRING, - Field::FIELD_CAST_BLOB, Field::FIELD_CAST_STOP}; +{Field::FIELD_CAST_STOP}; // Array of pointers on conversion table for all fields types casting static Field::field_cast_enum *field_cast_array[]= {0, //FIELD_CAST_STOP @@ -286,18 +305,26 @@ static Field::field_cast_enum *field_cast_array[]= }; +/* + Check if field of given type can store a value of this field. + + SYNOPSIS + type type for test + + RETURN + 1 can + 0 can not +*/ + bool Field::field_cast_compatible(Field::field_cast_enum type) { DBUG_ASSERT(type != FIELD_CAST_STOP); Field::field_cast_enum *array= field_cast_array[field_cast_type()]; - uint i= 0; - Field::field_cast_enum tp; - do + while (*array != FIELD_CAST_STOP) { - tp= array[i++]; - if (tp == type) + if (*(array++) == type) return 1; - } while (tp != FIELD_CAST_STOP); + } return 0; } @@ -441,11 +468,9 @@ bool Field::get_time(TIME *ltime) void Field::store_time(TIME *ltime,timestamp_type type) { - char buff[MAX_DATE_REP_LENGTH]; - String tmp; - tmp.set(buff, sizeof(buff), &my_charset_bin); - TIME_to_string(ltime, &tmp); - store(buff, tmp.length(), &my_charset_bin); + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= (uint) my_TIME_to_str(ltime, buff); + store(buff, length, &my_charset_bin); } @@ -2905,11 +2930,12 @@ void Field_double::sql_type(String &res) const */ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs) - :Field_str(ptr_arg, 19, (uchar*) 0,0, + :Field_str(ptr_arg, 19, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg, cs) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ @@ -2925,23 +2951,41 @@ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, /* - Sets TABLE::timestamp_default_now and TABLE::timestamp_on_update_now - members according to unireg type of this TIMESTAMP field. - + Get auto-set type for TIMESTAMP field. + SYNOPSIS - Field_timestamp::set_timestamp_offsets() - + get_auto_set_type() + + DESCRIPTION + Returns value indicating during which operations this TIMESTAMP field + should be auto-set to current timestamp. */ -void Field_timestamp::set_timestamp_offsets() +timestamp_auto_set_type Field_timestamp::get_auto_set_type() const { - ulong timestamp= (ulong) (ptr - (char*) table->record[0]) + 1; - - DBUG_ASSERT(table->timestamp_field == this && unireg_check != NONE); - - table->timestamp_default_now= - (unireg_check == TIMESTAMP_UN_FIELD)? 0 : timestamp; - table->timestamp_on_update_now= - (unireg_check == TIMESTAMP_DN_FIELD)? 0 : timestamp; + switch (unireg_check) + { + case TIMESTAMP_DN_FIELD: + return TIMESTAMP_AUTO_SET_ON_INSERT; + case TIMESTAMP_UN_FIELD: + return TIMESTAMP_AUTO_SET_ON_UPDATE; + case TIMESTAMP_OLD_FIELD: + /* + Altough we can have several such columns in legacy tables this + function should be called only for first of them (i.e. the one + having auto-set property). + */ + DBUG_ASSERT(table->timestamp_field == this); + /* Fall-through */ + case TIMESTAMP_DNUN_FIELD: + return TIMESTAMP_AUTO_SET_ON_BOTH; + default: + /* + Normally this function should not be called for TIMESTAMPs without + auto-set property. + */ + DBUG_ASSERT(0); + return TIMESTAMP_NO_AUTO_SET; + } } @@ -3249,6 +3293,7 @@ void Field_timestamp::sql_type(String &res) const void Field_timestamp::set_time() { long tmp= (long) table->in_use->query_start(); + set_notnull(); #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) { @@ -3475,8 +3520,8 @@ bool Field_time::send_binary(Protocol *protocol) { TIME tm; Field_time::get_time(&tm); - tm.day= tm.hour/3600; // Move hours to days - tm.hour-= tm.day*3600; + tm.day= tm.hour/24; // Move hours to days + tm.hour-= tm.day*24; return protocol->store_time(&tm); } @@ -4258,9 +4303,12 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) /* Convert character set if nesessary */ if (String::needs_conversion(length, cs, field_charset, ¬_used)) { - tmpstr.copy(from, length, cs, field_charset); + uint conv_errors; + tmpstr.copy(from, length, cs, field_charset, &conv_errors); from= tmpstr.ptr(); length= tmpstr.length(); + if (conv_errors) + error= 1; } /* @@ -4283,11 +4331,11 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) from+= field_charset->cset->scan(field_charset, from, end, MY_SEQ_SPACES); if (from != end) - { - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); - error=1; - } + error= 1; } + if (error) + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); + return error; } @@ -4309,8 +4357,9 @@ int Field_str::store(double nr) uint length; bool use_scientific_notation= TRUE; use_scientific_notation= TRUE; -if (field_length < 32 && fabs(nr) < log_10[field_length]-1) + if (field_length < 32 && fabs(nr) < log_10[field_length]-1) use_scientific_notation= FALSE; + length= (uint) my_sprintf(buff, (buff, "%-.*g", (use_scientific_notation ? max(0, (int)field_length-5) : @@ -4325,7 +4374,7 @@ if (field_length < 32 && fabs(nr) < log_10[field_length]-1) like inserting 500.0 in char(1) */ DBUG_ASSERT(field_length < 5 || length <= field_length+1); - return store((const char *)buff, min(length, field_length), charset()); + return store((const char *) buff, length, charset()); } @@ -4407,21 +4456,8 @@ void Field_string::sql_type(String &res) const res.length(length); } - char *Field_string::pack(char *to, const char *from, uint max_length) { - const char *end=from+min(field_length,max_length); - uchar length; - while (end > from && end[-1] == ' ') - end--; - *to= length=(uchar) (end-from); - memcpy(to+1, from, (int) length); - return to+1+length; -} - - -char *Field_string::pack_key(char *to, const char *from, uint max_length) -{ uint length= min(field_length,max_length); uint char_length= max_length/field_charset->mbmaxlen; if (length > char_length) @@ -4429,15 +4465,24 @@ char *Field_string::pack_key(char *to, const char *from, uint max_length) set_if_smaller(length, char_length); while (length && from[length-1] == ' ') length--; - *to= (uchar)length; - memcpy(to+1, from, length); - return to+1+length; + *to++= (char) (uchar) length; + if (field_length > 255) + *to++= (char) (uchar) (length >> 8); + memcpy(to, from, length); + return to+length; } const char *Field_string::unpack(char *to, const char *from) { - uint length= (uint) (uchar) *from++; + uint length; + if (field_length > 255) + { + length= uint2korr(from); + from+= 2; + } + else + length= (uint) (uchar) *from++; memcpy(to, from, (int) length); bfill(to+length, field_length - length, ' '); return from+length; @@ -4446,8 +4491,19 @@ const char *Field_string::unpack(char *to, const char *from) int Field_string::pack_cmp(const char *a, const char *b, uint length) { - uint a_length= (uint) (uchar) *a++; - uint b_length= (uint) (uchar) *b++; + uint a_length, b_length; + if (field_length > 255) + { + a_length= uint2korr(a); + b_length= uint2korr(b); + a+= 2; + b+= 2; + } + else + { + a_length= (uint) (uchar) *a++; + b_length= (uint) (uchar) *b++; + } return my_strnncoll(field_charset, (const uchar*)a,a_length, (const uchar*)b,b_length); @@ -4456,7 +4512,14 @@ int Field_string::pack_cmp(const char *a, const char *b, uint length) int Field_string::pack_cmp(const char *b, uint length) { - uint b_length= (uint) (uchar) *b++; + uint b_length; + if (field_length > 255) + { + b_length= uint2korr(b); + b+= 2; + } + else + b_length= (uint) (uchar) *b++; char *end= ptr + field_length; while (end > ptr && end[-1] == ' ') end--; @@ -4496,16 +4559,20 @@ int Field_varstring::store(const char *from,uint length,CHARSET_INFO *cs) /* Convert character set if nesessary */ if (String::needs_conversion(length, cs, field_charset, ¬_used)) { - tmpstr.copy(from, length, cs, field_charset); + uint conv_errors; + tmpstr.copy(from, length, cs, field_charset, &conv_errors); from= tmpstr.ptr(); length= tmpstr.length(); + if (conv_errors) + error= 1; } if (length > field_length) { length=field_length; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); error= 1; } + if (error) + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); memcpy(ptr+HA_KEY_BLOB_LENGTH,from,length); int2store(ptr, length); return error; @@ -4833,6 +4900,7 @@ void Field_blob::put_length(char *pos, uint32 length) int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) { + int error= 0; if (!length) { bzero(ptr,Field_blob::pack_length()); @@ -4849,9 +4917,12 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) if ((was_conversion= String::needs_conversion(length, cs, field_charset, ¬_used))) { - tmpstr.copy(from, length, cs, field_charset); + uint conv_errors; + tmpstr.copy(from, length, cs, field_charset, &conv_errors); from= tmpstr.ptr(); length= tmpstr.length(); + if (conv_errors) + error= 1; } copy_length= max_data_length(); @@ -4865,8 +4936,7 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) min(length, copy_length), copy_length); if (copy_length < length) - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); - + error= 1; Field_blob::store_length(copy_length); if (was_conversion || table->copy_blobs || copy_length <= MAX_FIELD_WIDTH) { // Must make a copy @@ -4878,6 +4948,8 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) } bmove(ptr+packlength,(char*) &from,sizeof(char*)); } + if (error) + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1); return 0; } @@ -4939,10 +5011,10 @@ String *Field_blob::val_str(String *val_buffer __attribute__((unused)), int Field_blob::cmp(const char *a,uint32 a_length, const char *b, uint32 b_length) { - int diff=my_strnncoll(field_charset, - (const uchar*)a,min(a_length,b_length), - (const uchar*)b,min(a_length,b_length)); - return diff ? diff : (int) (a_length - b_length); + return field_charset->coll->strnncoll(field_charset, + (const uchar*)a, a_length, + (const uchar*)b, b_length, + 0); } @@ -5024,6 +5096,11 @@ void Field_blob::get_key_image(char *buff,uint length, } #endif /*HAVE_SPATIAL*/ + get_ptr(&blob); + uint char_length= length / cs->mbmaxlen; + char_length= my_charpos(cs, blob, blob + blob_length, char_length); + set_if_smaller(blob_length, char_length); + if ((uint32) length > blob_length) { /* @@ -5034,7 +5111,6 @@ void Field_blob::get_key_image(char *buff,uint length, length=(uint) blob_length; } int2store(buff,length); - get_ptr(&blob); memcpy(buff+HA_KEY_BLOB_LENGTH, blob, length); } @@ -5050,6 +5126,10 @@ int Field_blob::key_cmp(const byte *key_ptr, uint max_key_length) char *blob1; uint blob_length=get_length(ptr); memcpy_fixed(&blob1,ptr+packlength,sizeof(char*)); + CHARSET_INFO *cs= charset(); + uint char_length= max_key_length / cs->mbmaxlen; + char_length= my_charpos(cs, blob1, blob1+blob_length, char_length); + set_if_smaller(blob_length, char_length); return Field_blob::cmp(blob1,min(blob_length, max_key_length), (char*) key_ptr+HA_KEY_BLOB_LENGTH, uint2korr(key_ptr)); @@ -5458,7 +5538,7 @@ int Field_enum::store(const char *from,uint length,CHARSET_INFO *cs) /* Remove end space */ while (length > 0 && my_isspace(system_charset_info,from[length-1])) length--; - uint tmp=find_type(typelib, from, length, 0); + uint tmp=find_type2(typelib, from, length, field_charset); if (!tmp) { if (length < 6) // Can't be more than 99999 enums @@ -5636,8 +5716,8 @@ int Field_set::store(const char *from,uint length,CHARSET_INFO *cs) from= tmpstr.ptr(); length= tmpstr.length(); } - ulonglong tmp= find_set(typelib, from, length, ¬_used, ¬_used2, - &got_warning); + ulonglong tmp= find_set(typelib, from, length, field_charset, + ¬_used, ¬_used2, &got_warning); if (!tmp && length && length < 22) { /* This is for reading numbers with LOAD DATA INFILE */ @@ -5944,8 +6024,9 @@ Field *make_field(char *ptr, uint32 field_length, f_is_zerofill(pack_flag) != 0, f_is_dec(pack_flag) == 0); case FIELD_TYPE_TIMESTAMP: - return new Field_timestamp(ptr,field_length, - unireg_check, field_name, table, field_charset); + return new Field_timestamp(ptr,field_length, null_pos, null_bit, + unireg_check, field_name, table, + field_charset); case FIELD_TYPE_YEAR: return new Field_year(ptr,field_length,null_pos,null_bit, unireg_check, field_name, table); diff --git a/sql/field.h b/sql/field.h index 725e962ca07..50ea1450085 100644 --- a/sql/field.h +++ b/sql/field.h @@ -179,7 +179,14 @@ public: virtual void make_field(Send_field *)=0; virtual void sort_string(char *buff,uint length)=0; virtual bool optimize_range(uint idx, uint part); - virtual bool store_for_compare() { return 0; } + /* + This should be true for fields which, when compared with constant + items, can be casted to longlong. In this case we will at 'fix_fields' + stage cast the constant items to longlongs and at the execution stage + use field->val_int() for comparison. Used to optimize clauses like + 'a_column BETWEEN date_const, date_const'. + */ + virtual bool can_be_compared_as_longlong() const { return FALSE; } virtual void free() {} Field *new_field(MEM_ROOT *root, struct st_table *new_table) { @@ -568,7 +575,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 8; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } uint32 max_length() { return 20; } field_cast_enum field_cast_type() { return FIELD_CAST_LONGLONG; } }; @@ -678,6 +685,7 @@ public: class Field_timestamp :public Field_str { public: Field_timestamp(char *ptr_arg, uint32 len_arg, + uchar *null_ptr_arg, uchar null_bit_arg, enum utype unireg_check_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs); @@ -696,7 +704,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 4; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } bool zero_pack() const { return 0; } void set_time(); virtual void set_default() @@ -707,8 +715,11 @@ public: else Field::set_default(); } - inline long get_timestamp() + /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */ + inline long get_timestamp(my_bool *null_value) { + if ((*null_value= is_null())) + return 0; #ifdef WORDS_BIGENDIAN if (table->db_low_byte_first) return sint4korr(ptr); @@ -720,7 +731,7 @@ public: bool get_date(TIME *ltime,uint fuzzydate); bool get_time(TIME *ltime); field_cast_enum field_cast_type() { return FIELD_CAST_TIMESTAMP; } - void set_timestamp_offsets(); + timestamp_auto_set_type get_auto_set_type() const; }; @@ -742,7 +753,7 @@ public: String *val_str(String*,String *); bool send_binary(Protocol *protocol); void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } field_cast_enum field_cast_type() { return FIELD_CAST_YEAR; } }; @@ -774,7 +785,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 4; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } bool zero_pack() const { return 1; } field_cast_enum field_cast_type() { return FIELD_CAST_DATE; } }; @@ -804,7 +815,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } bool zero_pack() const { return 1; } bool get_date(TIME *ltime,uint fuzzydate); bool get_time(TIME *ltime); @@ -841,7 +852,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 3; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } bool zero_pack() const { return 1; } field_cast_enum field_cast_type() { return FIELD_CAST_TIME; } }; @@ -877,7 +888,7 @@ public: void sort_string(char *buff,uint length); uint32 pack_length() const { return 8; } void sql_type(String &str) const; - bool store_for_compare() { return 1; } + bool can_be_compared_as_longlong() const { return TRUE; } bool zero_pack() const { return 1; } bool get_date(TIME *ltime,uint fuzzydate); bool get_time(TIME *ltime); @@ -918,7 +929,6 @@ public: void sort_string(char *buff,uint length); void sql_type(String &str) const; char *pack(char *to, const char *from, uint max_length=~(uint) 0); - char *pack_key(char *to, const char *from, uint max_length); const char *unpack(char* to, const char *from); int pack_cmp(const char *a,const char *b,uint key_length); int pack_cmp(const char *b,uint key_length); diff --git a/sql/field_conv.cc b/sql/field_conv.cc index d7993939092..890687fc925 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -164,7 +164,8 @@ set_field_to_null_with_conversions(Field *field, bool no_conversions) /* Check if this is a special type, which will get a special walue - when set to NULL + when set to NULL (TIMESTAMP fields which allow setting to NULL + are handled by first check). */ if (field->type() == FIELD_TYPE_TIMESTAMP) { @@ -251,7 +252,8 @@ static void do_copy_timestamp(Copy_field *copy) { if (*copy->from_null_ptr & copy->from_bit) { - ((Field_timestamp*) copy->to_field)->set_time();// Same as set_field_to_null + /* Same as in set_field_to_null_with_conversions() */ + ((Field_timestamp*) copy->to_field)->set_time(); } else (copy->do_copy2)(copy); @@ -261,7 +263,11 @@ static void do_copy_timestamp(Copy_field *copy) static void do_copy_next_number(Copy_field *copy) { if (*copy->from_null_ptr & copy->from_bit) - copy->to_field->reset(); // Same as set_field_to_null + { + /* Same as in set_field_to_null_with_conversions() */ + copy->to_field->table->auto_increment_field_not_null= FALSE; + copy->to_field->reset(); + } else (copy->do_copy2)(copy); } @@ -436,18 +442,20 @@ void Copy_field::set(Field *to,Field *from,bool save) } } else - do_copy=do_copy_not_null; + { + if (to_field->type() == FIELD_TYPE_TIMESTAMP) + do_copy= do_copy_timestamp; // Automatic timestamp + else if (to_field == to_field->table->next_number_field) + do_copy= do_copy_next_number; + else + do_copy= do_copy_not_null; + } } else if (to_field->real_maybe_null()) { to_null_ptr= to->null_ptr; to_bit= to->null_bit; - if (to_field->type() == FIELD_TYPE_TIMESTAMP) - do_copy=do_copy_timestamp; // Automatic timestamp - else if (to_field == to_field->table->next_number_field) - do_copy=do_copy_next_number; - else - do_copy=do_copy_maybe_null; + do_copy= do_copy_maybe_null; } else do_copy=0; diff --git a/sql/filesort.cc b/sql/filesort.cc index ef8148616e5..3e56acefea9 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -179,7 +179,13 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length, else #endif { - records=table->file->estimate_number_of_rows(); + records= table->file->estimate_rows_upper_bound(); + /* + If number of records is not known, use as much of sort buffer + as possible. + */ + if (records == HA_POS_ERROR) + records--; // we use 'records+1' below. selected_records_file= 0; } @@ -327,7 +333,7 @@ static char **make_char_array(register uint fields, uint length, myf my_flag) } /* make_char_array */ - /* Read all buffer pointers into memory */ +/* Read 'count' number of buffer pointers into memory */ static BUFFPEK *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count) { @@ -348,8 +354,40 @@ static BUFFPEK *read_buffpek_from_file(IO_CACHE *buffpek_pointers, uint count) } - - /* Search after sort_keys and place them in a temp. file */ +/* + Search after sort_keys and write them into tempfile. + SYNOPSIS + find_all_keys() + param Sorting parameter + select Use this to get source data + sort_keys Array of pointers to sort key + addon buffers. + buffpek_pointers File to write BUFFPEKs describing sorted segments + in tempfile. + tempfile File to write sorted sequences of sortkeys to. + indexfile If !NULL, use it for source data (contains rowids) + + NOTE + Basic idea: + while (get_next_sortkey()) + { + if (no free space in sort_keys buffers) + { + sort sort_keys buffer; + dump sorted sequence to 'tempfile'; + dump BUFFPEK describing sequence location into 'buffpek_pointers'; + } + put sort key into 'sort_keys'; + } + if (sort_keys has some elements && dumped at least once) + sort-dump-dump as above; + else + don't sort, leave sort_keys array to be sorted by caller. + + All produced sequences are guaranteed to be non-empty. + RETURN + Number of records written on success. + HA_POS_ERROR on error. +*/ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, uchar **sort_keys, @@ -489,7 +527,25 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, } /* find_all_keys */ - /* Skriver en buffert med nycklar till filen */ +/* + Sort the buffer and write: + 1) the sorted sequence to tempfile + 2) a BUFFPEK describing the sorted sequence position to buffpek_pointers + (was: Skriver en buffert med nycklar till filen) + SYNOPSIS + write_keys() + param Sort parameters + sort_keys Array of pointers to keys to sort + count Number of elements in sort_keys array + buffpek_pointers One 'BUFFPEK' struct will be written into this file. + The BUFFPEK::{file_pos, count} will indicate where + the sorted data was stored. + tempfile The sorted sequence will be written into this file. + + RETURN + 0 OK + 1 Error +*/ static int write_keys(SORTPARAM *param, register uchar **sort_keys, uint count, @@ -854,7 +910,21 @@ void reuse_freed_buff(QUEUE *queue, BUFFPEK *reuse, uint key_length) /* - Merge buffers to one buffer + Merge buffers to one buffer + SYNOPSIS + merge_buffers() + param Sort parameter + from_file File with source data (BUFFPEKs point to this file) + to_file File to write the sorted result data. + sort_buffer Buffer for data to store up to MERGEBUFF2 sort keys. + lastbuff OUT Store here BUFFPEK describing data written to to_file + Fb First element in source BUFFPEKs array + Tb Last element in source BUFFPEKs array + flag + + RETURN + 0 - OK + other - error */ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, @@ -893,6 +963,9 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, strpos= (uchar*) sort_buffer; org_max_rows=max_rows= param->max_rows; + /* The following will fire if there is not enough space in sort_buffer */ + DBUG_ASSERT(maxcount!=0); + if (init_queue(&queue, (uint) (Tb-Fb)+1, offsetof(BUFFPEK,key), 0, (queue_compare) (cmp= get_ptr_compare(sort_length)), (void*) &sort_length)) diff --git a/sql/ha_berkeley.cc b/sql/ha_berkeley.cc index bbe73ee8674..a5d0023b875 100644 --- a/sql/ha_berkeley.cc +++ b/sql/ha_berkeley.cc @@ -25,7 +25,7 @@ We will need an updated Berkeley DB version for this. - Killing threads that has got a 'deadlock' - SHOW TABLE STATUS should give more information about the table. - - Get a more accurate count of the number of rows (estimate_number_of_rows()). + - Get a more accurate count of the number of rows (estimate_rows_upper_bound()). We could store the found number of rows when the table is scanned and then increment the counter for each attempted write. - We will need to extend the manager thread to makes checkpoints at @@ -63,7 +63,7 @@ #define HA_BERKELEY_ROWS_IN_TABLE 10000 /* to get optimization right */ #define HA_BERKELEY_RANGE_COUNT 100 #define HA_BERKELEY_MAX_ROWS 10000000 /* Max rows in table */ -/* extra rows for estimate_number_of_rows() */ +/* extra rows for estimate_rows_upper_bound() */ #define HA_BERKELEY_EXTRA_ROWS 100 /* Bits for share->status */ @@ -90,7 +90,7 @@ const char *berkeley_lock_names[] = u_int32_t berkeley_lock_types[]= { DB_LOCK_DEFAULT, DB_LOCK_OLDEST, DB_LOCK_RANDOM }; TYPELIB berkeley_lock_typelib= {array_elements(berkeley_lock_names)-1,"", - berkeley_lock_names}; + berkeley_lock_names, NULL}; static void berkeley_print_error(const char *db_errpfx, char *buffer); static byte* bdb_get_key(BDB_SHARE *share,uint *length, @@ -856,8 +856,8 @@ int ha_berkeley::write_row(byte * record) DBUG_ENTER("write_row"); statistic_increment(table->in_use->status_var.ha_write_count, &LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && record == table->record[0]) update_auto_increment(); if ((error=pack_row(&row, record,1))) @@ -1104,8 +1104,8 @@ int ha_berkeley::update_row(const byte * old_row, byte * new_row) LINT_INIT(error); statistic_increment(table->in_use->status_var.ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_row+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); if (hidden_primary_key) { @@ -2566,7 +2566,7 @@ end: Used when sorting to allocate buffers and by the optimizer. */ -ha_rows ha_berkeley::estimate_number_of_rows() +ha_rows ha_berkeley::estimate_rows_upper_bound() { return share->rows + HA_BERKELEY_EXTRA_ROWS; } diff --git a/sql/ha_berkeley.h b/sql/ha_berkeley.h index d4fb4ca5fbb..e485b12bdb4 100644 --- a/sql/ha_berkeley.h +++ b/sql/ha_berkeley.h @@ -100,7 +100,7 @@ class ha_berkeley: public handler ulong table_flags(void) const { return int_table_flags; } uint max_supported_keys() const { return MAX_KEY-1; } uint extra_rec_buf_length() { return BDB_HIDDEN_PRIMARY_KEY_LENGTH; } - ha_rows estimate_number_of_rows(); + ha_rows estimate_rows_upper_bound(); const key_map *keys_to_use_for_scanning() { return &key_map_full; } bool has_transactions() { return 1;} diff --git a/sql/ha_heap.cc b/sql/ha_heap.cc index 6dc5e85e1d4..3bb8383e488 100644 --- a/sql/ha_heap.cc +++ b/sql/ha_heap.cc @@ -87,8 +87,8 @@ void ha_heap::set_keys_for_scanning(void) int ha_heap::write_row(byte * buf) { statistic_increment(current_thd->status_var.ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return heap_write(file,buf); @@ -97,8 +97,8 @@ int ha_heap::write_row(byte * buf) int ha_heap::update_row(const byte * old_data, byte * new_data) { statistic_increment(current_thd->status_var.ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return heap_update(file,old_data,new_data); } @@ -380,7 +380,8 @@ ha_rows ha_heap::records_in_range(uint inx, key_range *min_key, if (key->algorithm == HA_KEY_ALG_BTREE) return hp_rb_records_in_range(file, inx, min_key, max_key); - if (min_key->length != max_key->length || + if (!min_key || !max_key || + min_key->length != max_key->length || min_key->length != key->key_length || min_key->flag != HA_READ_KEY_EXACT || max_key->flag != HA_READ_AFTER_KEY) @@ -430,12 +431,8 @@ int ha_heap::create(const char *name, TABLE *table_arg, seg->type= field->key_type(); else { - if (!f_is_packed(flag) && - f_packtype(flag) == (int) FIELD_TYPE_DECIMAL && - !(field->charset() == &my_charset_bin)) - seg->type= (int) HA_KEYTYPE_TEXT; - else - seg->type= (int) HA_KEYTYPE_BINARY; + if ((seg->type = field->key_type()) != (int) HA_KEYTYPE_TEXT) + seg->type= HA_KEYTYPE_BINARY; } seg->start= (uint) key_part->offset; seg->length= (uint) key_part->length; diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index 222e2b4ce8a..efd74a543c2 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -20,10 +20,9 @@ NOTE: You can only use noninlined InnoDB functions in this file, because we have disables the InnoDB inlining in this file. */ /* TODO list for the InnoDB handler in 4.1: - - Check if the query_id is now right also in prepared and executed stats - in build_template() - - Add multi-language char set support to CREATE TABLE and the comparison - of strings + - Remove the flag innodb_active_trans from thd and replace it with a + function call innodb_active_trans(thd), which looks at the InnoDB + trx struct state field - Find out what kind of problems the OS X case-insensitivity causes to table and database names; should we 'normalize' the names like we do in Windows? @@ -41,6 +40,7 @@ have disables the InnoDB inlining in this file. */ #include <hash.h> #include <myisampack.h> #include <mysys_err.h> +#include <my_sys.h> #define MAX_ULONG_BIT ((ulong) 1 << (sizeof(ulong)*8-1)) @@ -117,6 +117,9 @@ uint innobase_flush_log_at_trx_commit = 1; my_bool innobase_log_archive = FALSE;/* unused */ my_bool innobase_use_native_aio = FALSE; my_bool innobase_fast_shutdown = TRUE; +my_bool innobase_very_fast_shutdown = FALSE; /* this can be set to + 1 just prior calling + innobase_end() */ my_bool innobase_file_per_table = FALSE; my_bool innobase_locks_unsafe_for_binlog = FALSE; my_bool innobase_create_status_file = FALSE; @@ -299,7 +302,7 @@ convert_error_code_to_mysql( } else if (error == (int) DB_CANNOT_DROP_CONSTRAINT) { - return(HA_ERR_CANNOT_ADD_FOREIGN); /* TODO: This is a bit + return(HA_ERR_ROW_IS_REFERENCED); /* TODO: This is a bit misleading, a new MySQL error code should be introduced */ } else if (error == (int) DB_COL_APPEARS_TWICE_IN_INDEX) { @@ -426,6 +429,36 @@ innobase_mysql_print_thd( putc('\n', f); } +/********************************************************************** +Compares NUL-terminated UTF-8 strings case insensitively. + +NOTE that the exact prototype of this function has to be in +/innobase/dict/dict0dict.c! */ +extern "C" +int +innobase_strcasecmp( +/*================*/ + /* out: 0 if a=b, <0 if a<b, >1 if a>b */ + const char* a, /* in: first string to compare */ + const char* b) /* in: second string to compare */ +{ + return(my_strcasecmp(system_charset_info, a, b)); +} + +/********************************************************************** +Makes all characters in a NUL-terminated UTF-8 string lower case. + +NOTE that the exact prototype of this function has to be in +/innobase/dict/dict0dict.c! */ +extern "C" +void +innobase_casedn_str( +/*================*/ + char* a) /* in/out: string to put in lower case */ +{ + my_casedn_str(system_charset_info, a); +} + /************************************************************************* Creates a temporary file. */ extern "C" @@ -684,14 +717,7 @@ innobase_query_caching_of_table_permitted( separator between db and table */ norm_name[full_name_len] = '\0'; #ifdef __WIN__ - /* Put to lower case */ - - char* ptr = norm_name; - - while (*ptr != '\0') { - *ptr = tolower(*ptr); - ptr++; - } + innobase_casedn_str(norm_name); #endif /* The call of row_search_.. will start a new transaction if it is not yet started */ @@ -736,15 +762,35 @@ innobase_invalidate_query_cache( } /********************************************************************* -Get the quote character to be used in SQL identifiers. */ +Get the quote character to be used in SQL identifiers. +This definition must match the one in innobase/ut/ut0ut.c! */ extern "C" -char -mysql_get_identifier_quote_char(void) -/*=================================*/ +int +mysql_get_identifier_quote_char( +/*============================*/ /* out: quote character to be - used in SQL identifiers */ + used in SQL identifiers; EOF if none */ + trx_t* trx, /* in: transaction */ + const char* name, /* in: name to print */ + ulint namelen)/* in: length of name */ +{ + if (!trx || !trx->mysql_thd) { + return(EOF); + } + return(get_quote_char_for_identifier((THD*) trx->mysql_thd, + name, namelen)); +} + +/************************************************************************** +Obtain a pointer to the MySQL THD object, as in current_thd(). This +definition must match the one in sql/ha_innodb.cc! */ +extern "C" +void* +innobase_current_thd(void) +/*======================*/ + /* out: MySQL THD object */ { - return '`'; + return(current_thd); } /********************************************************************* @@ -782,6 +828,10 @@ ha_innobase::init_table_handle_for_HANDLER(void) trx_assign_read_view(prebuilt->trx); + /* Set the MySQL flag to mark that there is an active transaction */ + + current_thd->transaction.all.innodb_active_trans = 1; + /* We did the necessary inits in this function, no need to repeat them in row_search_for_mysql */ @@ -791,6 +841,7 @@ ha_innobase::init_table_handle_for_HANDLER(void) if the trx isolation level would have been specified as SERIALIZABLE */ prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; /* Always fetch all columns in the index record */ @@ -829,7 +880,7 @@ innobase_init(void) Note that when using the embedded server, the datadirectory is not necessarily the current directory of this program. */ - if (mysql_embedded) { + if (mysqld_embedded) { default_path = mysql_real_data_home; fil_path_to_mysql_datadir = mysql_real_data_home; } else { @@ -967,7 +1018,7 @@ innobase_init(void) srv_max_n_open_files = (ulint) innobase_open_files; srv_innodb_status = (ibool) innobase_create_status_file; - srv_print_verbose_log = mysql_embedded ? 0 : 1; + srv_print_verbose_log = mysqld_embedded ? 0 : 1; /* Store the default charset-collation number of this MySQL installation */ @@ -1043,6 +1094,15 @@ innobase_end(void) #endif if (innodb_inited) { + if (innobase_very_fast_shutdown) { + srv_very_fast_shutdown = TRUE; + fprintf(stderr, +"InnoDB: MySQL has requested a very fast shutdown without flushing\n" +"InnoDB: the InnoDB buffer pool to data files. At the next mysqld startup\n" +"InnoDB: InnoDB will do a crash recovery!\n"); + + } + innodb_inited= 0; if (innobase_shutdown_for_mysql() != DB_SUCCESS) err= 1; @@ -1100,6 +1160,48 @@ innobase_commit_low( } /********************************************************************* +Creates an InnoDB transaction struct for the thd if it does not yet have one. +Starts a new InnoDB transaction if a transaction is not yet started. And +assigns a new snapshot for a consistent read if the transaction does not yet +have one. */ + +int +innobase_start_trx_and_assign_read_view( +/*====================================*/ + /* out: 0 */ + THD* thd) /* in: MySQL thread handle of the user for whom + the transaction should be committed */ +{ + trx_t* trx; + + DBUG_ENTER("innobase_start_trx_and_assign_read_view"); + + /* Create a new trx struct for thd, if it does not yet have one */ + + trx = check_trx_exists(thd); + + /* This is just to play safe: release a possible FIFO ticket and + search latch. Since we will reserve the kernel mutex, we have to + release the search system latch first to obey the latching order. */ + + innobase_release_stat_resources(trx); + + /* If the transaction is not started yet, start it */ + + trx_start_if_not_started_noninline(trx); + + /* Assign a read view if the transaction does not have it yet */ + + trx_assign_read_view(trx); + + /* Set the MySQL flag to mark that there is an active transaction */ + + current_thd->transaction.all.innodb_active_trans = 1; + + DBUG_RETURN(0); +} + +/********************************************************************* Commits a transaction in an InnoDB database or marks an SQL statement ended. */ @@ -1130,8 +1232,10 @@ innobase_commit( 1. ::external_lock(), 2. ::start_stmt(), - 3. innobase_query_caching_of_table_permitted(), and + 3. innobase_query_caching_of_table_permitted(), 4. innobase_savepoint(), + 5. ::init_table_handle_for_HANDLER(), + 6. innobase_start_trx_and_assign_read_view() and it is only set to 0 in a commit or a rollback. If it is 0 we know there cannot be resources to be freed and we could return immediately. @@ -1460,14 +1564,7 @@ normalize_table_name( norm_name[name_ptr - db_ptr - 1] = '/'; #ifdef __WIN__ - /* Put to lower case */ - - ptr = norm_name; - - while (*ptr != '\0') { - *ptr = tolower(*ptr); - ptr++; - } + innobase_casedn_str(norm_name); #endif } @@ -1485,12 +1582,14 @@ ha_innobase::open( { dict_table_t* ib_table; char norm_name[1000]; + THD* thd; DBUG_ENTER("ha_innobase::open"); UT_NOT_USED(mode); UT_NOT_USED(test_if_locked); + thd = current_thd; normalize_table_name(norm_name, name); user_thd = NULL; @@ -1540,7 +1639,7 @@ ha_innobase::open( DBUG_RETURN(1); } - if (ib_table->ibd_file_missing && !current_thd->tablespace_op) { + if (ib_table->ibd_file_missing && !thd->tablespace_op) { ut_print_timestamp(stderr); fprintf(stderr, " InnoDB error:\n" "MySQL is trying to open a table handle but the .ibd file for\n" @@ -2106,11 +2205,6 @@ build_template( templ = prebuilt->mysql_template + n_requested_fields; field = table->field[i]; - /* TODO: Check if the query_id is now right also in prepared - and executed SQL statements. Previously, MySQL-4.1 failed to - update field->query_id so that the formula - thd->query_id == field->query_id did not work. */ - ibool index_contains_field= dict_index_contains_col_or_prefix(index, i); @@ -2221,8 +2315,8 @@ ha_innobase::write_row( statistic_increment(current_thd->status_var.ha_write_count, &LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record + table->timestamp_default_now - 1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; @@ -2504,8 +2598,8 @@ ha_innobase::update_row( ut_ad(prebuilt->trx == (trx_t*) current_thd->transaction.all.innobase_tid); - if (table->timestamp_on_update_now) - update_timestamp(new_row + table->timestamp_on_update_now - 1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; @@ -2778,7 +2872,7 @@ ha_innobase::index_read( (ulint)upd_and_key_val_buff_len, index, (byte*) key_ptr, - (ulint) key_len); + (ulint) key_len, prebuilt->trx); } else { /* We position the cursor to the last or the first entry in the index */ @@ -3068,7 +3162,7 @@ ha_innobase::index_last( { int error; - DBUG_ENTER("index_first"); + DBUG_ENTER("index_last"); statistic_increment(current_thd->status_var.ha_read_last_count, &LOCK_status); @@ -3261,7 +3355,15 @@ create_table_def( trx_t* trx, /* in: InnoDB transaction handle */ TABLE* form, /* in: information on table columns and indexes */ - const char* table_name) /* in: table name */ + const char* table_name, /* in: table name */ + const char* path_of_temp_table)/* in: if this is a table explicitly + created by the user with the + TEMPORARY keyword, then this + parameter is the dir path where the + table should be placed if we create + an .ibd file for it (no .ibd extension + in the path, though); otherwise this + is NULL */ { Field* field; dict_table_t* table; @@ -3284,6 +3386,11 @@ create_table_def( table = dict_mem_table_create((char*) table_name, 0, n_cols); + if (path_of_temp_table) { + table->dir_path_of_temp_table = + mem_heap_strdup(table->heap, path_of_temp_table); + } + for (i = 0; i < n_cols; i++) { field = form->field[i]; @@ -3364,8 +3471,7 @@ create_index( ind_type = 0; - if (key_num == form->primary_key) - { + if (key_num == form->primary_key) { ind_type = ind_type | DICT_CLUSTERED; } @@ -3392,9 +3498,9 @@ create_index( field = form->field[j]; - if (0 == ut_cmp_in_lower_case( - (char*)field->field_name, - (char*)key_part->field->field_name)) { + if (0 == innobase_strcasecmp( + field->field_name, + key_part->field->field_name)) { /* Found the corresponding column */ break; @@ -3425,10 +3531,6 @@ create_index( prefix_len = 0; } - if (prefix_len >= DICT_MAX_COL_PREFIX_LEN) { - DBUG_RETURN(-1); - } - /* We assume all fields should be sorted in ascending order, hence the '0': */ @@ -3534,7 +3636,7 @@ ha_innobase::create( srv_lower_case_table_names = FALSE; } - fn_format(name2, name, "", "",2); // Remove the .frm extension + fn_format(name2, name, "", "", 2); // Remove the .frm extension normalize_table_name(norm_name, name2); @@ -3546,8 +3648,13 @@ ha_innobase::create( /* Create the table definition in InnoDB */ - error = create_table_def(trx, form, norm_name); - + if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { + + error = create_table_def(trx, form, norm_name, name2); + } else { + error = create_table_def(trx, form, norm_name, NULL); + } + if (error) { innobase_commit_low(trx); @@ -3622,8 +3729,8 @@ ha_innobase::create( } if (current_thd->query != NULL) { - LEX_STRING q; + if (thd->convert_string(&q, system_charset_info, current_thd->query, current_thd->query_length, @@ -3835,7 +3942,7 @@ innobase_drop_database( namebuf[len] = '/'; namebuf[len + 1] = '\0'; #ifdef __WIN__ - my_casedn_str(system_charset_info, namebuf); + innobase_casedn_str(namebuf); #endif trx = trx_allocate_for_mysql(); trx->mysql_thd = current_thd; @@ -3997,14 +4104,16 @@ ha_innobase::records_in_range( index, (byte*) (min_key ? min_key->key : (const mysql_byte*) 0), - (ulint) (min_key ? min_key->length : 0)); + (ulint) (min_key ? min_key->length : 0), + prebuilt->trx); row_sel_convert_mysql_key_to_innobase( range_end, (byte*) key_val_buff2, buff2_len, index, (byte*) (max_key ? max_key->key : (const mysql_byte*) 0), - (ulint) (max_key ? max_key->length : 0)); + (ulint) (max_key ? max_key->length : 0), + prebuilt->trx); mode1 = convert_search_mode_to_innobase(min_key ? min_key->flag : HA_READ_KEY_EXACT); @@ -4038,7 +4147,7 @@ Gives an UPPER BOUND to the number of rows in a table. This is used in filesort.cc. */ ha_rows -ha_innobase::estimate_number_of_rows(void) +ha_innobase::estimate_rows_upper_bound(void) /*======================================*/ /* out: upper bound of rows */ { @@ -4047,7 +4156,7 @@ ha_innobase::estimate_number_of_rows(void) ulonglong estimate; ulonglong local_data_file_length; - DBUG_ENTER("estimate_number_of_rows"); + DBUG_ENTER("estimate_rows_upper_bound"); /* We do not know if MySQL can call this function before calling external_lock(). To be safe, update the thd of the current table @@ -4127,7 +4236,7 @@ ha_innobase::read_time( time_for_scan = scan_time(); - if ((total_rows = estimate_number_of_rows()) < rows) + if ((total_rows = estimate_rows_upper_bound()) < rows) return time_for_scan; return (ranges + (double) rows / (double) total_rows * time_for_scan); @@ -4148,6 +4257,8 @@ ha_innobase::info( ha_rows rec_per_key; ulong j; ulong i; + char path[FN_REFLEN]; + os_file_stat_t stat_info; DBUG_ENTER("info"); @@ -4157,7 +4268,7 @@ ha_innobase::info( if (srv_force_recovery >= SRV_FORCE_NO_IBUF_MERGE) { - return; + DBUG_VOID_RETURN; } /* We do not know if MySQL can call this function before calling @@ -4185,6 +4296,26 @@ ha_innobase::info( prebuilt->trx->op_info = (char*) "returning various info to MySQL"; + + if (ib_table->space != 0) { + my_snprintf(path, sizeof(path), "%s/%s%s", + mysql_data_home, ib_table->name, + ".ibd"); + unpack_filename(path,path); + } else { + my_snprintf(path, sizeof(path), "%s/%s%s", + mysql_data_home, ib_table->name, + reg_ext); + + unpack_filename(path,path); + } + + /* Note that we do not know the access time of the table, + nor the CHECK TABLE time, nor the UPDATE or INSERT time. */ + + if (os_file_get_status(path,&stat_info)) { + create_time = stat_info.ctime; + } } if (flag & HA_STATUS_VARIABLE) { @@ -4391,7 +4522,8 @@ ha_innobase::update_table_comment( (ulong) fsp_get_available_space_in_free_extents( prebuilt->table->space)); - dict_print_info_on_foreign_keys(FALSE, file, prebuilt->table); + dict_print_info_on_foreign_keys(FALSE, file, + prebuilt->trx, prebuilt->table); flen = ftell(file); if(length + flen + 3 > 64000) { flen = 64000 - 3 - length; @@ -4457,7 +4589,8 @@ ha_innobase::get_foreign_key_create_info(void) trx_search_latch_release_if_reserved(prebuilt->trx); /* output the data to a temporary file */ - dict_print_info_on_foreign_keys(TRUE, file, prebuilt->table); + dict_print_info_on_foreign_keys(TRUE, file, + prebuilt->trx, prebuilt->table); prebuilt->trx->op_info = (char*)""; flen = ftell(file); @@ -4632,23 +4765,41 @@ ha_innobase::start_stmt( prepared for an update of a row */ prebuilt->select_lock_type = LOCK_X; - } else { - if (thd->lex->sql_command == SQLCOM_SELECT - && thd->lex->lock_option == TL_READ) { - - /* For other than temporary tables, we obtain - no lock for consistent read (plain SELECT) */ - - prebuilt->select_lock_type = LOCK_NONE; - } else { - /* Not a consistent read: use LOCK_X as the - select_lock_type value (TODO: how could we know - whether it should be LOCK_S, LOCK_X, or LOCK_NONE?) */ - - prebuilt->select_lock_type = LOCK_X; - } - } + } else { + if (trx->isolation_level != TRX_ISO_SERIALIZABLE + && thd->lex->sql_command == SQLCOM_SELECT + && thd->lex->lock_option == TL_READ) { + /* For other than temporary tables, we obtain + no lock for consistent read (plain SELECT). */ + + prebuilt->select_lock_type = LOCK_NONE; + } else { + /* Not a consistent read: restore the + select_lock_type value. The value of + stored_select_lock_type was decided in: + 1) ::store_lock(), + 2) ::external_lock(), and + 3) ::init_table_handle_for_HANDLER(). */ + + prebuilt->select_lock_type = + prebuilt->stored_select_lock_type; + } + + if (prebuilt->stored_select_lock_type != LOCK_S + && prebuilt->stored_select_lock_type != LOCK_X) { + fprintf(stderr, +"InnoDB: Error: stored_select_lock_type is %lu inside ::start_stmt()!\n", + prebuilt->stored_select_lock_type); + + /* Set the value to LOCK_X: this is just fault + tolerance, we do not know what the correct value + should be! */ + + prebuilt->select_lock_type = LOCK_X; + } + } + /* Set the MySQL flag to mark that there is an active transaction */ thd->transaction.all.innodb_active_trans = 1; @@ -4723,6 +4874,7 @@ ha_innobase::external_lock( /* If this is a SELECT, then it is in UPDATE TABLE ... or SELECT ... FOR UPDATE */ prebuilt->select_lock_type = LOCK_X; + prebuilt->stored_select_lock_type = LOCK_X; } if (lock_type != F_UNLCK) { @@ -4758,7 +4910,8 @@ ha_innobase::external_lock( } if (prebuilt->select_lock_type != LOCK_NONE) { - if (thd->in_lock_tables) { + if (thd->in_lock_tables && + thd->variables.innodb_table_locks) { ulint error; error = row_lock_table_for_mysql(prebuilt); @@ -4975,14 +5128,22 @@ ha_innobase::store_lock( { row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt; - if (lock_type == TL_READ_WITH_SHARED_LOCKS || + if ((lock_type == TL_READ && thd->in_lock_tables) || + (lock_type == TL_READ_HIGH_PRIORITY && thd->in_lock_tables) || + lock_type == TL_READ_WITH_SHARED_LOCKS || lock_type == TL_READ_NO_INSERT) { - /* This is a SELECT ... IN SHARE MODE, or - we are doing a complex SQL statement like + /* The OR cases above are in this order: + 1) MySQL is doing LOCK TABLES ... READ LOCAL, or + 2) (we do not know when TL_READ_HIGH_PRIORITY is used), or + 3) this is a SELECT ... IN SHARE MODE, or + 4) we are doing a complex SQL statement like INSERT INTO ... SELECT ... and the logical logging (MySQL - binlog) requires the use of a locking read */ + binlog) requires the use of a locking read, or + MySQL is doing LOCK TABLES ... READ. */ prebuilt->select_lock_type = LOCK_S; + prebuilt->stored_select_lock_type = LOCK_S; + } else if (lock_type != TL_IGNORE) { /* In ha_berkeley.cc there is a comment that MySQL @@ -4993,6 +5154,7 @@ ha_innobase::store_lock( here even if this would be SELECT ... FOR UPDATE */ prebuilt->select_lock_type = LOCK_NONE; + prebuilt->stored_select_lock_type = LOCK_NONE; } if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) { @@ -5155,14 +5317,13 @@ ha_innobase::get_auto_increment() } /*********************************************************************** -This function stores binlog offset and flushes logs */ +This function stores the binlog offset and flushes logs. */ void innobase_store_binlog_offset_and_flush_log( -/*=============================*/ +/*=======================================*/ char *binlog_name, /* in: binlog name */ - longlong offset /* in: binlog offset */ -) + longlong offset) /* in: binlog offset */ { mtr_t mtr; @@ -5172,7 +5333,7 @@ innobase_store_binlog_offset_and_flush_log( mtr_start_noninline(&mtr); /* Update the latest MySQL binlog name and offset info - in trx sys header */ + in trx sys header */ trx_sys_update_mysql_binlog_offset( binlog_name, @@ -5235,18 +5396,121 @@ ha_innobase::cmp_ref( return 0; } -char *ha_innobase::get_mysql_bin_log_name() +char* +ha_innobase::get_mysql_bin_log_name() { - return trx_sys_mysql_bin_log_name; + return(trx_sys_mysql_bin_log_name); } -ulonglong ha_innobase::get_mysql_bin_log_pos() +ulonglong +ha_innobase::get_mysql_bin_log_pos() { - /* - trx... is ib_longlong, which is a typedef for a 64-bit integer (__int64 or - longlong) so it's ok to cast it to ulonglong. - */ - return trx_sys_mysql_bin_log_pos; + /* trx... is ib_longlong, which is a typedef for a 64-bit integer + (__int64 or longlong) so it's ok to cast it to ulonglong. */ + + return(trx_sys_mysql_bin_log_pos); +} + +extern "C" { +/********************************************************************** +This function is used to find the storage length in bytes of the first n +characters for prefix indexes using a multibyte character set. The function +finds charset information and returns length of prefix_len characters in the +index field in bytes. + +NOTE: the prototype of this function is copied to data0type.c! If you change +this function, you MUST change also data0type.c! */ + +ulint +innobase_get_at_most_n_mbchars( +/*===========================*/ + /* out: number of bytes occupied by the first + n characters */ + ulint charset_id, /* in: character set id */ + ulint prefix_len, /* in: prefix length in bytes of the index + (this has to be divided by mbmaxlen to get the + number of CHARACTERS n in the prefix) */ + ulint data_len, /* in: length of the string in bytes */ + const char* str) /* in: character string */ +{ + ulint char_length; /* character length in bytes */ + ulint n_chars; /* number of characters in prefix */ + CHARSET_INFO* charset; /* charset used in the field */ + + charset = get_charset(charset_id, MYF(MY_WME)); + + ut_ad(charset); + ut_ad(charset->mbmaxlen); + + /* Calculate how many characters at most the prefix index contains */ + + n_chars = prefix_len / charset->mbmaxlen; + + /* If the charset is multi-byte, then we must find the length of the + first at most n chars in the string. If the string contains less + characters than n, then we return the length to the end of the last + character. */ + + if (charset->mbmaxlen > 1) { + /* my_charpos() returns the byte length of the first n_chars + characters, or a value bigger than the length of str, if + there were not enough full characters in str. + + Why does the code below work: + Suppose that we are looking for n UTF-8 characters. + + 1) If the string is long enough, then the prefix contains at + least n complete UTF-8 characters + maybe some extra + characters + an incomplete UTF-8 character. No problem in + this case. The function returns the pointer to the + end of the nth character. + + 2) If the string is not long enough, then the string contains + the complete value of a column, that is, only complete UTF-8 + characters, and we can store in the column prefix index the + whole string. */ + + char_length = my_charpos(charset, str, + str + data_len, n_chars); + if (char_length > data_len) { + char_length = data_len; + } + } else { + if (data_len < prefix_len) { + char_length = data_len; + } else { + char_length = prefix_len; + } + } + + return(char_length); +} +} + +extern "C" { +/********************************************************************** +This function returns true if SQL-query in the current thread +is either REPLACE or LOAD DATA INFILE REPLACE. +NOTE that /mysql/innobase/row/row0ins.c must contain the +prototype for this function ! */ + +ibool +innobase_query_is_replace(void) +/*===========================*/ +{ + THD* thd; + + thd = (THD *)innobase_current_thd(); + + if ( thd->lex->sql_command == SQLCOM_REPLACE || + thd->lex->sql_command == SQLCOM_REPLACE_SELECT || + ( thd->lex->sql_command == SQLCOM_LOAD && + thd->lex->duplicates == DUP_REPLACE )) { + return true; + } else { + return false; + } +} } #endif /* HAVE_INNOBASE_DB */ diff --git a/sql/ha_innodb.h b/sql/ha_innodb.h index fa3e9f012e8..57e136a8fba 100644 --- a/sql/ha_innodb.h +++ b/sql/ha_innodb.h @@ -150,7 +150,7 @@ class ha_innobase: public handler void position(byte *record); ha_rows records_in_range(uint inx, key_range *min_key, key_range *max_key); - ha_rows estimate_number_of_rows(); + ha_rows estimate_rows_upper_bound(); int create(const char *name, register TABLE *form, HA_CREATE_INFO *create_info); @@ -192,8 +192,16 @@ extern my_bool innobase_log_archive, innobase_use_native_aio, innobase_fast_shutdown, innobase_file_per_table, innobase_locks_unsafe_for_binlog, innobase_create_status_file; +extern my_bool innobase_very_fast_shutdown; /* set this to 1 just before + calling innobase_end() if you want + InnoDB to shut down without + flushing the buffer pool: this + is equivalent to a 'crash' */ extern "C" { extern ulong srv_max_buf_pool_modified_pct; +extern ulong srv_max_purge_lag; +extern ulong srv_auto_extend_increment; +extern ulong srv_max_purge_lag; } extern TYPELIB innobase_lock_typelib; @@ -229,3 +237,5 @@ my_bool innobase_query_caching_of_table_permitted(THD* thd, char* full_name, void innobase_release_temporary_latches(void* innobase_tid); void innobase_store_binlog_offset_and_flush_log(char *binlog_name,longlong offset); + +int innobase_start_trx_and_assign_read_view(THD* thd); diff --git a/sql/ha_isam.cc b/sql/ha_isam.cc index 201230c8aab..d703df7d2e3 100644 --- a/sql/ha_isam.cc +++ b/sql/ha_isam.cc @@ -70,8 +70,8 @@ uint ha_isam::min_record_length(uint options) const int ha_isam::write_row(byte * buf) { statistic_increment(current_thd->status_var.ha_write_count, &LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return !nisam_write(file,buf) ? 0 : my_errno ? my_errno : -1; @@ -80,8 +80,8 @@ int ha_isam::write_row(byte * buf) int ha_isam::update_row(const byte * old_data, byte * new_data) { statistic_increment(current_thd->status_var.ha_update_count, &LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return !nisam_update(file,old_data,new_data) ? 0 : my_errno ? my_errno : -1; } diff --git a/sql/ha_isammrg.cc b/sql/ha_isammrg.cc index 19983e4b257..5a070087724 100644 --- a/sql/ha_isammrg.cc +++ b/sql/ha_isammrg.cc @@ -78,8 +78,8 @@ int ha_isammrg::write_row(byte * buf) int ha_isammrg::update_row(const byte * old_data, byte * new_data) { statistic_increment(current_thd->status_var.ha_update_count, &LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return !mrg_update(file,old_data,new_data) ? 0 : my_errno ? my_errno : -1; } diff --git a/sql/ha_myisam.cc b/sql/ha_myisam.cc index a738e535f14..7482c6d5fa8 100644 --- a/sql/ha_myisam.cc +++ b/sql/ha_myisam.cc @@ -37,7 +37,7 @@ ulong myisam_recover_options= HA_RECOVER_NONE; const char *myisam_recover_names[] = { "DEFAULT", "BACKUP", "FORCE", "QUICK", NullS}; TYPELIB myisam_recover_typelib= {array_elements(myisam_recover_names)-1,"", - myisam_recover_names}; + myisam_recover_names, NULL}; /***************************************************************************** @@ -252,8 +252,8 @@ int ha_myisam::write_row(byte * buf) statistic_increment(current_thd->status_var.ha_write_count,&LOCK_status); /* If we have a timestamp column, update it to the current time */ - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); /* If we have an auto_increment column and we are writing a changed row @@ -510,16 +510,16 @@ int ha_myisam::repair(THD* thd, HA_CHECK_OPT *check_opt) (uint) (T_RETRY_WITHOUT_QUICK | T_QUICK))) { param.testflag&= ~T_RETRY_WITHOUT_QUICK; - sql_print_error("Note: Retrying repair of: '%s' without quick", - table->path); + sql_print_information("Retrying repair of: '%s' without quick", + table->path); continue; } param.testflag&= ~T_QUICK; if ((param.testflag & T_REP_BY_SORT)) { param.testflag= (param.testflag & ~T_REP_BY_SORT) | T_REP; - sql_print_error("Note: Retrying repair of: '%s' with keycache", - table->path); + sql_print_information("Retrying repair of: '%s' with keycache", + table->path); continue; } break; @@ -528,16 +528,17 @@ int ha_myisam::repair(THD* thd, HA_CHECK_OPT *check_opt) !(check_opt->flags & T_VERY_SILENT)) { char llbuff[22],llbuff2[22]; - sql_print_error("Note: Found %s of %s rows when repairing '%s'", - llstr(file->state->records, llbuff), - llstr(start_records, llbuff2), - table->path); + sql_print_information("Found %s of %s rows when repairing '%s'", + llstr(file->state->records, llbuff), + llstr(start_records, llbuff2), + table->path); } return error; } int ha_myisam::optimize(THD* thd, HA_CHECK_OPT *check_opt) { + int error; if (!file) return HA_ADMIN_INTERNAL_ERROR; MI_CHECK param; @@ -547,7 +548,14 @@ int ha_myisam::optimize(THD* thd, HA_CHECK_OPT *check_opt) param.testflag = (check_opt->flags | T_SILENT | T_FORCE_CREATE | T_REP_BY_SORT | T_STATISTICS | T_SORT_INDEX); param.sort_buffer_length= check_opt->sort_buffer_size; - return repair(thd,param,1); + if ((error= repair(thd,param,1)) && param.retry_repair) + { + sql_print_warning("Warning: Optimize table got errno %d, retrying", + my_errno); + param.testflag&= ~T_REP_BY_SORT; + error= repair(thd,param,1); + } + return error; } @@ -914,7 +922,13 @@ int ha_myisam::enable_indexes(uint mode) param.myf_rw&= ~MY_WAIT_IF_FULL; param.sort_buffer_length= thd->variables.myisam_sort_buff_size; param.tmpdir=&mysql_tmpdir_list; - error=repair(thd,param,0) != HA_ADMIN_OK; + if ((error= (repair(thd,param,0) != HA_ADMIN_OK)) && param.retry_repair) + { + sql_print_warning("Warning: Enabling keys got errno %d, retrying", + my_errno); + param.testflag&= ~(T_REP_BY_SORT | T_QUICK); + error= (repair(thd,param,0) != HA_ADMIN_OK); + } info(HA_STATUS_CONST); thd->proc_info=save_proc_info; } @@ -1035,7 +1049,7 @@ bool ha_myisam::check_and_repair(THD *thd) // Don't use quick if deleted rows if (!file->state->del && (myisam_recover_options & HA_RECOVER_QUICK)) check_opt.flags|=T_QUICK; - sql_print_error("Warning: Checking table: '%s'",table->path); + sql_print_warning("Checking table: '%s'",table->path); old_query= thd->query; old_query_length= thd->query_length; @@ -1046,7 +1060,7 @@ bool ha_myisam::check_and_repair(THD *thd) if ((marked_crashed= mi_is_crashed(file)) || check(thd, &check_opt)) { - sql_print_error("Warning: Recovering table: '%s'",table->path); + sql_print_warning("Recovering table: '%s'",table->path); check_opt.flags= ((myisam_recover_options & HA_RECOVER_BACKUP ? T_BACKUP_DATA : 0) | (marked_crashed ? 0 : T_QUICK) | @@ -1071,8 +1085,8 @@ bool ha_myisam::is_crashed() const int ha_myisam::update_row(const byte * old_data, byte * new_data) { statistic_increment(current_thd->status_var.ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return mi_update(file,old_data,new_data); } @@ -1283,6 +1297,7 @@ int ha_myisam::delete_table(const char *name) return mi_delete_table(name); } + int ha_myisam::external_lock(THD *thd, int lock_type) { return mi_lock_database(file, !table->tmp_table ? @@ -1290,7 +1305,6 @@ int ha_myisam::external_lock(THD *thd, int lock_type) F_UNLCK : F_EXTRA_LCK)); } - THR_LOCK_DATA **ha_myisam::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) diff --git a/sql/ha_myisammrg.cc b/sql/ha_myisammrg.cc index 29a31571380..903dd9dec2f 100644 --- a/sql/ha_myisammrg.cc +++ b/sql/ha_myisammrg.cc @@ -82,8 +82,8 @@ int ha_myisammrg::close(void) int ha_myisammrg::write_row(byte * buf) { statistic_increment(current_thd->status_var.ha_write_count,&LOCK_status); - if (table->timestamp_default_now) - update_timestamp(buf+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); if (table->next_number_field && buf == table->record[0]) update_auto_increment(); return myrg_write(file,buf); @@ -92,8 +92,8 @@ int ha_myisammrg::write_row(byte * buf) int ha_myisammrg::update_row(const byte * old_data, byte * new_data) { statistic_increment(current_thd->status_var.ha_update_count,&LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); return myrg_update(file,old_data,new_data); } diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 1837500d8f7..d59ab919862 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -20,7 +20,6 @@ NDB Cluster */ - #ifdef __GNUC__ #pragma implementation // gcc: Class implementation #endif @@ -33,9 +32,6 @@ #include <ndbapi/NdbApi.hpp> #include <ndbapi/NdbScanFilter.hpp> -#define USE_DISCOVER_ON_STARTUP -//#define USE_NDB_POOL - // Default value for parallelism static const int parallelism= 240; @@ -49,11 +45,13 @@ static const ha_rows autoincrement_prefetch= 32; // connectstring to cluster if given by mysqld const char *ndbcluster_connectstring= 0; +static const char *ha_ndb_ext=".ndb"; + #define NDB_HIDDEN_PRIMARY_KEY_LENGTH 8 #define ERR_PRINT(err) \ - DBUG_PRINT("error", ("Error: %d message: %s", err.code, err.message)) + DBUG_PRINT("error", ("%d message: %s", err.code, err.message)) #define ERR_RETURN(err) \ { \ @@ -63,13 +61,14 @@ const char *ndbcluster_connectstring= 0; // Typedefs for long names typedef NdbDictionary::Column NDBCOL; -typedef NdbDictionary::Table NDBTAB; +typedef NdbDictionary::Table NDBTAB; typedef NdbDictionary::Index NDBINDEX; typedef NdbDictionary::Dictionary NDBDICT; -bool ndbcluster_inited= false; +bool ndbcluster_inited= FALSE; static Ndb* g_ndb= NULL; +static Ndb_cluster_connection* g_ndb_cluster_connection= NULL; // Handler synchronization pthread_mutex_t ndbcluster_mutex; @@ -86,6 +85,16 @@ static int packfrm(const void *data, uint len, const void **pack_data, uint *pac static int unpackfrm(const void **data, uint *len, const void* pack_data); +static int ndb_get_table_statistics(Ndb*, const char *, + Uint64* rows, Uint64* commits); + + +/* + Dummy buffer to read zero pack_length fields + which are mapped to 1 char +*/ +static byte dummy_buf[1]; + /* Error handling functions */ @@ -103,7 +112,9 @@ static const err_code_mapping err_map[]= { 893, HA_ERR_FOUND_DUPP_UNIQUE }, { 721, HA_ERR_TABLE_EXIST }, { 4244, HA_ERR_TABLE_EXIST }, - { 241, HA_ERR_OLD_METADATA }, + + { 709, HA_ERR_NO_SUCH_TABLE }, + { 284, HA_ERR_NO_SUCH_TABLE }, { 266, HA_ERR_LOCK_WAIT_TIMEOUT }, { 274, HA_ERR_LOCK_WAIT_TIMEOUT }, @@ -118,6 +129,8 @@ static const err_code_mapping err_map[]= { 827, HA_ERR_RECORD_FILE_FULL }, { 832, HA_ERR_RECORD_FILE_FULL }, + { 0, 1 }, + { -1, -1 } }; @@ -127,13 +140,158 @@ static int ndb_to_mysql_error(const NdbError *err) uint i; for (i=0 ; err_map[i].ndb_err != err->code ; i++) { - if (err_map[i].my_err == -1) + if (err_map[i].my_err == -1){ + // Push the NDB error message as warning + push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_ERROR, + ER_GET_ERRMSG, ER(ER_GET_ERRMSG), + err->code, err->message, "NDB"); return err->code; + } } return err_map[i].my_err; } + +inline +int execute_no_commit(ha_ndbcluster *h, NdbConnection *trans) +{ + int m_batch_execute= 0; +#ifdef NOT_USED + if (m_batch_execute) + return 0; +#endif + return trans->execute(NoCommit,AbortOnError,1); +} + +inline +int execute_commit(ha_ndbcluster *h, NdbConnection *trans) +{ + int m_batch_execute= 0; +#ifdef NOT_USED + if (m_batch_execute) + return 0; +#endif + return trans->execute(Commit,AbortOnError,1); +} + +inline +int execute_no_commit_ie(ha_ndbcluster *h, NdbConnection *trans) +{ + int m_batch_execute= 0; +#ifdef NOT_USED + if (m_batch_execute) + return 0; +#endif + return trans->execute(NoCommit,IgnoreError,1); +} + +/* + Place holder for ha_ndbcluster thread specific data +*/ + +Thd_ndb::Thd_ndb() +{ + ndb= new Ndb(g_ndb_cluster_connection, ""); + lock_count= 0; + count= 0; + error= 0; +} + +Thd_ndb::~Thd_ndb() +{ + if (ndb) + delete ndb; +} + +/* + * manage uncommitted insert/deletes during transactio to get records correct + */ + +struct Ndb_table_local_info { + int no_uncommitted_rows_count; + ulong last_count; + ha_rows records; +}; + +void ha_ndbcluster::set_rec_per_key() +{ + DBUG_ENTER("ha_ndbcluster::get_status_const"); + for (uint i=0 ; i < table->keys ; i++) + { + table->key_info[i].rec_per_key[table->key_info[i].key_parts-1]= 1; + } + DBUG_VOID_RETURN; +} + +void ha_ndbcluster::records_update() +{ + DBUG_ENTER("ha_ndbcluster::records_update"); + struct Ndb_table_local_info *info= (struct Ndb_table_local_info *)m_table_info; + DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", + ((const NDBTAB *)m_table)->getTableId(), + info->no_uncommitted_rows_count)); + // if (info->records == ~(ha_rows)0) + { + Uint64 rows; + if(ndb_get_table_statistics(m_ndb, m_tabname, &rows, 0) == 0){ + info->records= rows; + } + } + { + THD *thd= current_thd; + if (((Thd_ndb*)(thd->transaction.thd_ndb))->error) + info->no_uncommitted_rows_count= 0; + } + records= info->records+ info->no_uncommitted_rows_count; + DBUG_VOID_RETURN; +} + +void ha_ndbcluster::no_uncommitted_rows_execute_failure() +{ + DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_execute_failure"); + THD *thd= current_thd; + ((Thd_ndb*)(thd->transaction.thd_ndb))->error= 1; + DBUG_VOID_RETURN; +} + +void ha_ndbcluster::no_uncommitted_rows_init(THD *thd) +{ + DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_init"); + struct Ndb_table_local_info *info= (struct Ndb_table_local_info *)m_table_info; + Thd_ndb *thd_ndb= (Thd_ndb *)thd->transaction.thd_ndb; + if (info->last_count != thd_ndb->count) + { + info->last_count = thd_ndb->count; + info->no_uncommitted_rows_count= 0; + info->records= ~(ha_rows)0; + DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", + ((const NDBTAB *)m_table)->getTableId(), + info->no_uncommitted_rows_count)); + } + DBUG_VOID_RETURN; +} + +void ha_ndbcluster::no_uncommitted_rows_update(int c) +{ + DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_update"); + struct Ndb_table_local_info *info= + (struct Ndb_table_local_info *)m_table_info; + info->no_uncommitted_rows_count+= c; + DBUG_PRINT("info", ("id=%d, no_uncommitted_rows_count=%d", + ((const NDBTAB *)m_table)->getTableId(), + info->no_uncommitted_rows_count)); + DBUG_VOID_RETURN; +} + +void ha_ndbcluster::no_uncommitted_rows_reset(THD *thd) +{ + DBUG_ENTER("ha_ndbcluster::no_uncommitted_rows_reset"); + ((Thd_ndb*)(thd->transaction.thd_ndb))->count++; + ((Thd_ndb*)(thd->transaction.thd_ndb))->error= 0; + DBUG_VOID_RETURN; +} + /* Take care of the error that occured in NDB @@ -142,12 +300,11 @@ static int ndb_to_mysql_error(const NdbError *err) # The mapped error code */ + int ha_ndbcluster::ndb_err(NdbConnection *trans) { int res; const NdbError err= trans->getNdbError(); - if (!err.code) - return 0; // Don't log things to DBUG log if no error DBUG_ENTER("ndb_err"); ERR_PRINT(err); @@ -157,6 +314,7 @@ int ha_ndbcluster::ndb_err(NdbConnection *trans) NDBDICT *dict= m_ndb->getDictionary(); DBUG_PRINT("info", ("invalidateTable %s", m_tabname)); dict->invalidateTable(m_tabname); + table->version=0L; /* Free when thread is ready */ break; } default: @@ -183,10 +341,11 @@ bool ha_ndbcluster::get_error_message(int error, DBUG_ENTER("ha_ndbcluster::get_error_message"); DBUG_PRINT("enter", ("error: %d", error)); - if (!m_ndb) - DBUG_RETURN(false); + Ndb *ndb= ((Thd_ndb*)current_thd->transaction.thd_ndb)->ndb; + if (!ndb) + DBUG_RETURN(FALSE); - const NdbError err= m_ndb->getNdbError(error); + const NdbError err= ndb->getNdbError(error); bool temporary= err.status==NdbError::TemporaryError; buf->set(err.message, strlen(err.message), &my_charset_bin); DBUG_PRINT("exit", ("message: %s, temporary: %d", buf->ptr(), temporary)); @@ -196,7 +355,8 @@ bool ha_ndbcluster::get_error_message(int error, /* Check if type is supported by NDB. - TODO Use this once, not in every operation + TODO Use this once in open(), not in every operation + */ static inline bool ndb_supported_type(enum_field_types type) @@ -224,12 +384,12 @@ static inline bool ndb_supported_type(enum_field_types type) case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_ENUM: case MYSQL_TYPE_SET: - return true; + return TRUE; case MYSQL_TYPE_NULL: case MYSQL_TYPE_GEOMETRY: break; } - return false; + return FALSE; } @@ -277,7 +437,7 @@ int ha_ndbcluster::set_ndb_key(NdbOperation *ndb_op, Field *field, */ int ha_ndbcluster::set_ndb_value(NdbOperation *ndb_op, Field *field, - uint fieldnr) + uint fieldnr, bool *set_blob_value) { const byte* field_ptr= field->ptr; uint32 pack_len= field->pack_length(); @@ -289,6 +449,13 @@ int ha_ndbcluster::set_ndb_value(NdbOperation *ndb_op, Field *field, if (ndb_supported_type(field->type())) { + // ndb currently does not support size 0 + const byte *empty_field= ""; + if (pack_len == 0) + { + pack_len= 1; + field_ptr= empty_field; + } if (! (field->flags & BLOB_FLAG)) { if (field->is_null()) @@ -312,14 +479,18 @@ int ha_ndbcluster::set_ndb_value(NdbOperation *ndb_op, Field *field, char* blob_ptr= NULL; field_blob->get_ptr(&blob_ptr); - // Looks like NULL blob can also be signaled in this way - if (blob_ptr == NULL) - DBUG_RETURN(ndb_blob->setNull() != 0); + // Looks like NULL ptr signals length 0 blob + if (blob_ptr == NULL) { + DBUG_ASSERT(blob_len == 0); + blob_ptr= (char*)""; + } DBUG_PRINT("value", ("set blob ptr=%x len=%u", (unsigned)blob_ptr, blob_len)); DBUG_DUMP("value", (char*)blob_ptr, min(blob_len, 26)); + if (set_blob_value) + *set_blob_value= TRUE; // No callback needed to write value DBUG_RETURN(ndb_blob->setValue(blob_ptr, blob_len) != 0); } @@ -414,7 +585,7 @@ int ha_ndbcluster::get_ndb_blobs_value(NdbBlob *last_ndb_blob) */ int ha_ndbcluster::get_ndb_value(NdbOperation *ndb_op, Field *field, - uint fieldnr) + uint fieldnr, byte* buf) { DBUG_ENTER("get_ndb_value"); DBUG_PRINT("enter", ("fieldnr: %d flags: %o", fieldnr, @@ -422,12 +593,19 @@ int ha_ndbcluster::get_ndb_value(NdbOperation *ndb_op, Field *field, if (field != NULL) { + DBUG_ASSERT(buf); if (ndb_supported_type(field->type())) { DBUG_ASSERT(field->ptr != NULL); if (! (field->flags & BLOB_FLAG)) - { - m_value[fieldnr].rec= ndb_op->getValue(fieldnr, field->ptr); + { + byte *field_buf; + if (field->pack_length() != 0) + field_buf= buf + (field->ptr - table->record[0]); + else + field_buf= dummy_buf; + m_value[fieldnr].rec= ndb_op->getValue(fieldnr, + field_buf); DBUG_RETURN(m_value[fieldnr].rec == NULL); } @@ -459,24 +637,24 @@ int ha_ndbcluster::get_ndb_value(NdbOperation *ndb_op, Field *field, bool ha_ndbcluster::uses_blob_value(bool all_fields) { if (table->blob_fields == 0) - return false; + return FALSE; if (all_fields) - return true; + return TRUE; { uint no_fields= table->fields; int i; - THD *thd= current_thd; + THD *thd= table->in_use; // They always put blobs at the end.. for (i= no_fields - 1; i >= 0; i--) { Field *field= table->field[i]; if (thd->query_id == field->query_id) { - return true; + return TRUE; } } } - return false; + return FALSE; } @@ -494,75 +672,77 @@ int ha_ndbcluster::get_metadata(const char *path) { NDBDICT *dict= m_ndb->getDictionary(); const NDBTAB *tab; - const void *data, *pack_data; - const char **key_name; - uint ndb_columns, mysql_columns, length, pack_length; int error; + bool invalidating_ndb_table= FALSE; + DBUG_ENTER("get_metadata"); DBUG_PRINT("enter", ("m_tabname: %s, path: %s", m_tabname, path)); - if (!(tab= dict->getTable(m_tabname))) - ERR_RETURN(dict->getNdbError()); - DBUG_PRINT("info", ("Table schema version: %d", tab->getObjectVersion())); - - /* - This is the place to check that the table we got from NDB - is equal to the one on local disk - */ - ndb_columns= (uint) tab->getNoOfColumns(); - mysql_columns= table->fields; - if (table->primary_key == MAX_KEY) - ndb_columns--; - if (ndb_columns != mysql_columns) - { - DBUG_PRINT("error", - ("Wrong number of columns, ndb: %d mysql: %d", - ndb_columns, mysql_columns)); - DBUG_RETURN(HA_ERR_OLD_METADATA); - } - - /* - Compare FrmData in NDB with frm file from disk. - */ - error= 0; - if (readfrm(path, &data, &length) || - packfrm(data, length, &pack_data, &pack_length)) - { - my_free((char*)data, MYF(MY_ALLOW_ZERO_PTR)); - my_free((char*)pack_data, MYF(MY_ALLOW_ZERO_PTR)); - DBUG_RETURN(1); - } + do { + const void *data, *pack_data; + uint length, pack_length; + + if (!(tab= dict->getTable(m_tabname))) + ERR_RETURN(dict->getNdbError()); + DBUG_PRINT("info", ("Table schema version: %d", tab->getObjectVersion())); + /* + Compare FrmData in NDB with frm file from disk. + */ + error= 0; + if (readfrm(path, &data, &length) || + packfrm(data, length, &pack_data, &pack_length)) + { + my_free((char*)data, MYF(MY_ALLOW_ZERO_PTR)); + my_free((char*)pack_data, MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(1); + } - if ((pack_length != tab->getFrmLength()) || - (memcmp(pack_data, tab->getFrmData(), pack_length))) - { - DBUG_PRINT("error", - ("metadata, pack_length: %d getFrmLength: %d memcmp: %d", - pack_length, tab->getFrmLength(), - memcmp(pack_data, tab->getFrmData(), pack_length))); - DBUG_DUMP("pack_data", (char*)pack_data, pack_length); - DBUG_DUMP("frm", (char*)tab->getFrmData(), tab->getFrmLength()); - error= HA_ERR_OLD_METADATA; - } - my_free((char*)data, MYF(0)); - my_free((char*)pack_data, MYF(0)); + if ((pack_length != tab->getFrmLength()) || + (memcmp(pack_data, tab->getFrmData(), pack_length))) + { + if (!invalidating_ndb_table) + { + DBUG_PRINT("info", ("Invalidating table")); + dict->invalidateTable(m_tabname); + invalidating_ndb_table= TRUE; + } + else + { + DBUG_PRINT("error", + ("metadata, pack_length: %d getFrmLength: %d memcmp: %d", + pack_length, tab->getFrmLength(), + memcmp(pack_data, tab->getFrmData(), pack_length))); + DBUG_DUMP("pack_data", (char*)pack_data, pack_length); + DBUG_DUMP("frm", (char*)tab->getFrmData(), tab->getFrmLength()); + error= 3; + invalidating_ndb_table= FALSE; + } + } + else + { + invalidating_ndb_table= FALSE; + } + my_free((char*)data, MYF(0)); + my_free((char*)pack_data, MYF(0)); + } while (invalidating_ndb_table); + if (error) DBUG_RETURN(error); - // All checks OK, lets use the table - m_table= (void*)tab; - + m_table= NULL; + m_table_info= NULL; + DBUG_RETURN(build_index_list(table, ILBP_OPEN)); } int ha_ndbcluster::build_index_list(TABLE *tab, enum ILBP phase) { + uint i; int error= 0; - char *name; - const char *index_name; + const char *name, *index_name; + char unique_index_name[FN_LEN]; static const char* unique_suffix= "$unique"; - uint i, name_len; KEY* key_info= tab->key_info; const char **key_name= tab->keynames.type_names; NdbDictionary::Dictionary *dict= m_ndb->getDictionary(); @@ -576,21 +756,15 @@ int ha_ndbcluster::build_index_list(TABLE *tab, enum ILBP phase) m_index[i].type= idx_type; if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX) { - name_len= strlen(index_name)+strlen(unique_suffix)+1; - // Create name for unique index by appending "$unique"; - if (!(name= my_malloc(name_len, MYF(MY_WME)))) - DBUG_RETURN(2); - strxnmov(name, name_len, index_name, unique_suffix, NullS); - m_index[i].unique_name= name; - DBUG_PRINT("info", ("Created unique index name: %s for index %d", - name, i)); + strxnmov(unique_index_name, FN_LEN, index_name, unique_suffix, NullS); + DBUG_PRINT("info", ("Created unique index name \'%s\' for index %d", + unique_index_name, i)); } // Create secondary indexes if in create phase if (phase == ILBP_CREATE) { - DBUG_PRINT("info", ("Creating index %u: %s", i, index_name)); - - switch (m_index[i].type){ + DBUG_PRINT("info", ("Creating index %u: %s", i, index_name)); + switch (idx_type){ case PRIMARY_KEY_INDEX: // Do nothing, already created @@ -600,16 +774,16 @@ int ha_ndbcluster::build_index_list(TABLE *tab, enum ILBP phase) break; case UNIQUE_ORDERED_INDEX: if (!(error= create_ordered_index(index_name, key_info))) - error= create_unique_index(get_unique_index_name(i), key_info); + error= create_unique_index(unique_index_name, key_info); break; case UNIQUE_INDEX: - error= create_unique_index(get_unique_index_name(i), key_info); + error= create_unique_index(unique_index_name, key_info); break; case ORDERED_INDEX: error= create_ordered_index(index_name, key_info); break; default: - DBUG_ASSERT(false); + DBUG_ASSERT(FALSE); break; } if (error) @@ -620,21 +794,20 @@ int ha_ndbcluster::build_index_list(TABLE *tab, enum ILBP phase) } } // Add handles to index objects - DBUG_PRINT("info", ("Trying to add handle to index %s", index_name)); - if ((m_index[i].type != PRIMARY_KEY_INDEX) && - (m_index[i].type != UNIQUE_INDEX)) + if (idx_type != PRIMARY_KEY_INDEX && idx_type != UNIQUE_INDEX) { + DBUG_PRINT("info", ("Get handle to index %s", index_name)); const NDBINDEX *index= dict->getIndex(index_name, m_tabname); if (!index) DBUG_RETURN(1); m_index[i].index= (void *) index; } - if (m_index[i].unique_name) + if (idx_type == UNIQUE_ORDERED_INDEX || idx_type == UNIQUE_INDEX) { - const NDBINDEX *index= dict->getIndex(m_index[i].unique_name, m_tabname); + DBUG_PRINT("info", ("Get handle to unique_index %s", unique_index_name)); + const NDBINDEX *index= dict->getIndex(unique_index_name, m_tabname); if (!index) DBUG_RETURN(1); m_index[i].unique_index= (void *) index; } - DBUG_PRINT("info", ("Added handle to index %s", index_name)); } DBUG_RETURN(error); @@ -665,13 +838,11 @@ void ha_ndbcluster::release_metadata() DBUG_PRINT("enter", ("m_tabname: %s", m_tabname)); m_table= NULL; + m_table_info= NULL; // Release index list for (i= 0; i < MAX_KEY; i++) { - if (m_index[i].unique_name) - my_free((char*)m_index[i].unique_name, MYF(0)); - m_index[i].unique_name= NULL; m_index[i].unique_index= NULL; m_index[i].index= NULL; } @@ -681,17 +852,12 @@ void ha_ndbcluster::release_metadata() int ha_ndbcluster::get_ndb_lock_type(enum thr_lock_type type) { - int lm; - if (type == TL_WRITE_ALLOW_WRITE) - lm= NdbScanOperation::LM_Exclusive; + if (type >= TL_WRITE_ALLOW_WRITE) + return NdbOperation::LM_Exclusive; else if (uses_blob_value(retrieve_all_fields)) - /* - TODO use a new scan mode to read + lock + keyinfo - */ - lm= NdbScanOperation::LM_Exclusive; + return NdbOperation::LM_Read; else - lm= NdbScanOperation::LM_CommittedRead; - return lm; + return NdbOperation::LM_CommittedRead; } static const ulong index_type_flags[]= @@ -709,33 +875,26 @@ static const ulong index_type_flags[]= through the index. */ // HA_KEYREAD_ONLY | - HA_READ_NEXT | - HA_READ_RANGE, + HA_READ_NEXT | + HA_READ_RANGE | + HA_READ_ORDER, /* UNIQUE_INDEX */ HA_ONLY_WHOLE_INDEX, /* UNIQUE_ORDERED_INDEX */ - HA_READ_NEXT | - HA_READ_RANGE, + HA_READ_NEXT | + HA_READ_RANGE | + HA_READ_ORDER, /* ORDERED_INDEX */ - HA_READ_NEXT | - HA_READ_RANGE, + HA_READ_NEXT | + HA_READ_RANGE | + HA_READ_ORDER }; static const int index_flags_size= sizeof(index_type_flags)/sizeof(ulong); -inline const char* ha_ndbcluster::get_index_name(uint idx_no) const -{ - return table->keynames.type_names[idx_no]; -} - -inline const char* ha_ndbcluster::get_unique_index_name(uint idx_no) const -{ - return m_index[idx_no].unique_name; -} - inline NDB_INDEX_TYPE ha_ndbcluster::get_index_type(uint idx_no) const { DBUG_ASSERT(idx_no < MAX_KEY); @@ -829,8 +988,10 @@ int ha_ndbcluster::pk_read(const byte *key, uint key_len, byte *buf) DBUG_PRINT("enter", ("key_len: %u", key_len)); DBUG_DUMP("key", (char*)key, key_len); - if (!(op= trans->getNdbOperation((NDBTAB *) m_table)) || - op->readTuple() != 0) + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); + if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || + op->readTuple(lm) != 0) ERR_RETURN(trans->getNdbError()); if (table->primary_key == MAX_KEY) @@ -842,7 +1003,7 @@ int ha_ndbcluster::pk_read(const byte *key, uint key_len, byte *buf) ERR_RETURN(trans->getNdbError()); // Read key at the same time, for future reference - if (get_ndb_value(op, NULL, no_fields)) + if (get_ndb_value(op, NULL, no_fields, NULL)) ERR_RETURN(trans->getNdbError()); } else @@ -859,7 +1020,7 @@ int ha_ndbcluster::pk_read(const byte *key, uint key_len, byte *buf) if ((thd->query_id == field->query_id) || retrieve_all_fields) { - if (get_ndb_value(op, field, i)) + if (get_ndb_value(op, field, i, buf)) ERR_RETURN(trans->getNdbError()); } else @@ -869,7 +1030,7 @@ int ha_ndbcluster::pk_read(const byte *key, uint key_len, byte *buf) } } - if (trans->execute(NoCommit, IgnoreError) != 0) + if (execute_no_commit_ie(this,trans) != 0) { table->status= STATUS_NOT_FOUND; DBUG_RETURN(ndb_err(trans)); @@ -898,8 +1059,10 @@ int ha_ndbcluster::complemented_pk_read(const byte *old_data, byte *new_data) // We have allready retrieved all fields, nothing to complement DBUG_RETURN(0); - if (!(op= trans->getNdbOperation((NDBTAB *) m_table)) || - op->readTuple() != 0) + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); + if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || + op->readTuple(lm) != 0) ERR_RETURN(trans->getNdbError()); int res; @@ -913,12 +1076,12 @@ int ha_ndbcluster::complemented_pk_read(const byte *old_data, byte *new_data) if (!(field->flags & PRI_KEY_FLAG) && (thd->query_id != field->query_id)) { - if (get_ndb_value(op, field, i)) + if (get_ndb_value(op, field, i, new_data)) ERR_RETURN(trans->getNdbError()); } } - if (trans->execute(NoCommit) != 0) + if (execute_no_commit(this,trans) != 0) { table->status= STATUS_NOT_FOUND; DBUG_RETURN(ndb_err(trans)); @@ -948,12 +1111,13 @@ int ha_ndbcluster::unique_index_read(const byte *key, DBUG_ENTER("unique_index_read"); DBUG_PRINT("enter", ("key_len: %u, index: %u", key_len, active_index)); DBUG_DUMP("key", (char*)key, key_len); - DBUG_PRINT("enter", ("name: %s", get_unique_index_name(active_index))); + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); if (!(op= trans->getNdbIndexOperation((NDBINDEX *) m_index[active_index].unique_index, - (NDBTAB *) m_table)) || - op->readTuple() != 0) + (const NDBTAB *) m_table)) || + op->readTuple(lm) != 0) ERR_RETURN(trans->getNdbError()); // Set secondary index key(s) @@ -976,7 +1140,7 @@ int ha_ndbcluster::unique_index_read(const byte *key, if ((thd->query_id == field->query_id) || (field->flags & PRI_KEY_FLAG)) { - if (get_ndb_value(op, field, i)) + if (get_ndb_value(op, field, i, buf)) ERR_RETURN(op->getNdbError()); } else @@ -986,7 +1150,7 @@ int ha_ndbcluster::unique_index_read(const byte *key, } } - if (trans->execute(NoCommit, IgnoreError) != 0) + if (execute_no_commit_ie(this,trans) != 0) { table->status= STATUS_NOT_FOUND; DBUG_RETURN(ndb_err(trans)); @@ -1022,7 +1186,7 @@ inline int ha_ndbcluster::next_result(byte *buf) If this an update or delete, call nextResult with false to process any records already cached in NdbApi */ - bool contact_ndb= m_lock.type != TL_WRITE_ALLOW_WRITE; + bool contact_ndb= m_lock.type < TL_WRITE_ALLOW_WRITE; do { DBUG_PRINT("info", ("Call nextResult, contact_ndb: %d", contact_ndb)); /* @@ -1030,10 +1194,10 @@ inline int ha_ndbcluster::next_result(byte *buf) */ if (ops_pending && blobs_pending) { - if (trans->execute(NoCommit) != 0) - DBUG_RETURN(ndb_err(trans)); + if (execute_no_commit(this,trans) != 0) + DBUG_RETURN(ndb_err(trans)); ops_pending= 0; - blobs_pending= false; + blobs_pending= FALSE; } check= cursor->nextResult(contact_ndb); if (check == 0) @@ -1056,9 +1220,22 @@ inline int ha_ndbcluster::next_result(byte *buf) be sent to NDB */ DBUG_PRINT("info", ("ops_pending: %d", ops_pending)); - if (ops_pending && (trans->execute(NoCommit) != 0)) - DBUG_RETURN(ndb_err(trans)); - ops_pending= 0; + if (ops_pending) + { + if (current_thd->transaction.on) + { + if (execute_no_commit(this,trans) != 0) + DBUG_RETURN(ndb_err(trans)); + } + else + { + if (execute_commit(this,trans) != 0) + DBUG_RETURN(ndb_err(trans)); + int res= trans->restart(); + DBUG_ASSERT(res == 0); + } + ops_pending= 0; + } contact_ndb= (check == 2); } @@ -1073,76 +1250,204 @@ inline int ha_ndbcluster::next_result(byte *buf) DBUG_RETURN(HA_ERR_END_OF_FILE); } - /* - Set bounds for a ordered index scan, use key_range + Set bounds for ordered index scan. */ int ha_ndbcluster::set_bounds(NdbIndexScanOperation *op, - const key_range *key, - int bound) + const key_range *keys[2]) { - uint key_len, key_store_len, tot_len, key_tot_len; - byte *key_ptr; - KEY* key_info= table->key_info + active_index; - KEY_PART_INFO* key_part= key_info->key_part; - KEY_PART_INFO* end= key_part+key_info->key_parts; - Field* field; - bool key_nullable, key_null; + const KEY *const key_info= table->key_info + active_index; + const uint key_parts= key_info->key_parts; + uint key_tot_len[2]; + uint tot_len; + uint i, j; DBUG_ENTER("set_bounds"); - DBUG_PRINT("enter", ("bound: %d", bound)); - DBUG_PRINT("enter", ("key_parts: %d", key_info->key_parts)); - DBUG_PRINT("enter", ("key->length: %d", key->length)); - DBUG_PRINT("enter", ("key->flag: %d", key->flag)); + DBUG_PRINT("info", ("key_parts=%d", key_parts)); - // Set bounds using key data + for (j= 0; j <= 1; j++) + { + const key_range *key= keys[j]; + if (key != NULL) + { + // for key->flag see ha_rkey_function + DBUG_PRINT("info", ("key %d length=%d flag=%d", + j, key->length, key->flag)); + key_tot_len[j]= key->length; + } + else + { + DBUG_PRINT("info", ("key %d not present", j)); + key_tot_len[j]= 0; + } + } tot_len= 0; - key_ptr= (byte *) key->key; - key_tot_len= key->length; - for (; key_part != end; key_part++) - { - field= key_part->field; - key_len= key_part->length; - key_store_len= key_part->store_length; - key_nullable= (bool) key_part->null_bit; - key_null= (field->maybe_null() && *key_ptr); - tot_len+= key_store_len; - - const char* bounds[]= {"LE", "LT", "GE", "GT", "EQ"}; - DBUG_ASSERT(bound >= 0 && bound <= 4); - DBUG_PRINT("info", ("Set Bound%s on %s %s %s %s", - bounds[bound], - field->field_name, - key_nullable ? "NULLABLE" : "", - key_null ? "NULL":"")); - DBUG_PRINT("info", ("Total length %ds", tot_len)); - - DBUG_DUMP("key", (char*) key_ptr, key_store_len); - - if (op->setBound(field->field_name, - bound, - key_null ? 0 : (key_nullable ? key_ptr + 1 : key_ptr), - key_null ? 0 : key_len) != 0) - ERR_RETURN(op->getNdbError()); - - key_ptr+= key_store_len; - if (tot_len >= key_tot_len) - break; + for (i= 0; i < key_parts; i++) + { + KEY_PART_INFO *key_part= &key_info->key_part[i]; + Field *field= key_part->field; + uint part_len= key_part->length; + uint part_store_len= key_part->store_length; + bool part_nullable= (bool) key_part->null_bit; + // Info about each key part + struct part_st { + bool part_last; + const key_range *key; + const byte *part_ptr; + bool part_null; + int bound_type; + const char* bound_ptr; + }; + struct part_st part[2]; + + for (j= 0; j <= 1; j++) + { + struct part_st &p = part[j]; + p.key= NULL; + p.bound_type= -1; + if (tot_len < key_tot_len[j]) + { + p.part_last= (tot_len + part_store_len >= key_tot_len[j]); + p.key= keys[j]; + p.part_ptr= &p.key->key[tot_len]; + p.part_null= (field->maybe_null() && *p.part_ptr); + p.bound_ptr= (const char *) + p.part_null ? 0 : part_nullable ? p.part_ptr + 1 : p.part_ptr; + + if (j == 0) + { + switch (p.key->flag) + { + case HA_READ_KEY_EXACT: + p.bound_type= NdbIndexScanOperation::BoundEQ; + break; + case HA_READ_KEY_OR_NEXT: + p.bound_type= NdbIndexScanOperation::BoundLE; + break; + case HA_READ_AFTER_KEY: + if (! p.part_last) + p.bound_type= NdbIndexScanOperation::BoundLE; + else + p.bound_type= NdbIndexScanOperation::BoundLT; + break; + default: + break; + } + } + if (j == 1) { + switch (p.key->flag) + { + case HA_READ_BEFORE_KEY: + if (! p.part_last) + p.bound_type= NdbIndexScanOperation::BoundGE; + else + p.bound_type= NdbIndexScanOperation::BoundGT; + break; + case HA_READ_AFTER_KEY: // weird + p.bound_type= NdbIndexScanOperation::BoundGE; + break; + default: + break; + } + } - /* - Only one bound which is not EQ can be set - so if this bound was not EQ, bail out and make - a best effort attempt - */ - if (bound != NdbIndexScanOperation::BoundEQ) - break; - } + if (p.bound_type == -1) + { + DBUG_PRINT("error", ("key %d unknown flag %d", j, p.key->flag)); + DBUG_ASSERT(false); + // Stop setting bounds but continue with what we have + DBUG_RETURN(0); + } + } + } + + // Seen with e.g. b = 1 and c > 1 + if (part[0].bound_type == NdbIndexScanOperation::BoundLE && + part[1].bound_type == NdbIndexScanOperation::BoundGE && + memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0) + { + DBUG_PRINT("info", ("replace LE/GE pair by EQ")); + part[0].bound_type= NdbIndexScanOperation::BoundEQ; + part[1].bound_type= -1; + } + // Not seen but was in previous version + if (part[0].bound_type == NdbIndexScanOperation::BoundEQ && + part[1].bound_type == NdbIndexScanOperation::BoundGE && + memcmp(part[0].part_ptr, part[1].part_ptr, part_store_len) == 0) + { + DBUG_PRINT("info", ("remove GE from EQ/GE pair")); + part[1].bound_type= -1; + } + for (j= 0; j <= 1; j++) + { + struct part_st &p = part[j]; + // Set bound if not done with this key + if (p.key != NULL) + { + DBUG_PRINT("info", ("key %d:%d offset=%d length=%d last=%d bound=%d", + j, i, tot_len, part_len, p.part_last, p.bound_type)); + DBUG_DUMP("info", (const char*)p.part_ptr, part_store_len); + + // Set bound if not cancelled via type -1 + if (p.bound_type != -1) + if (op->setBound(field->field_name, p.bound_type, p.bound_ptr)) + ERR_RETURN(op->getNdbError()); + } + } + + tot_len+= part_store_len; + } DBUG_RETURN(0); } +inline +int ha_ndbcluster::define_read_attrs(byte* buf, NdbOperation* op) +{ + uint i; + THD *thd= current_thd; + NdbConnection *trans= m_active_trans; + + DBUG_ENTER("define_read_attrs"); + + // Define attributes to read + for (i= 0; i < table->fields; i++) + { + Field *field= table->field[i]; + if ((thd->query_id == field->query_id) || + (field->flags & PRI_KEY_FLAG) || + retrieve_all_fields) + { + if (get_ndb_value(op, field, i, buf)) + ERR_RETURN(op->getNdbError()); + } + else + { + m_value[i].ptr= NULL; + } + } + + if (table->primary_key == MAX_KEY) + { + DBUG_PRINT("info", ("Getting hidden key")); + // Scanning table with no primary key + int hidden_no= table->fields; +#ifndef DBUG_OFF + const NDBTAB *tab= (const NDBTAB *) m_table; + if (!tab->getColumn(hidden_no)) + DBUG_RETURN(1); +#endif + if (get_ndb_value(op, NULL, hidden_no, NULL)) + ERR_RETURN(op->getNdbError()); + } + + if (execute_no_commit(this,trans) != 0) + DBUG_RETURN(ndb_err(trans)); + DBUG_PRINT("exit", ("Scan started successfully")); + DBUG_RETURN(next_result(buf)); +} /* Start ordered index scan in NDB @@ -1152,52 +1457,60 @@ int ha_ndbcluster::ordered_index_scan(const key_range *start_key, const key_range *end_key, bool sorted, byte* buf) { + bool restart; NdbConnection *trans= m_active_trans; NdbResultSet *cursor; NdbIndexScanOperation *op; - const char *index_name; DBUG_ENTER("ordered_index_scan"); DBUG_PRINT("enter", ("index: %u, sorted: %d", active_index, sorted)); DBUG_PRINT("enter", ("Starting new ordered scan on %s", m_tabname)); - - index_name= get_index_name(active_index); - if (!(op= trans->getNdbIndexScanOperation((NDBINDEX *) - m_index[active_index].index, - (NDBTAB *) m_table))) - ERR_RETURN(trans->getNdbError()); - NdbScanOperation::LockMode lm= (NdbScanOperation::LockMode) - get_ndb_lock_type(m_lock.type); - if (!(cursor= op->readTuples(lm, 0, parallelism, sorted))) - ERR_RETURN(trans->getNdbError()); - m_active_cursor= cursor; + // Check that sorted seems to be initialised + DBUG_ASSERT(sorted == 0 || sorted == 1); + + if (m_active_cursor == 0) + { + restart= false; + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); + if (!(op= trans->getNdbIndexScanOperation((NDBINDEX *) + m_index[active_index].index, + (const NDBTAB *) m_table)) || + !(cursor= op->readTuples(lm, 0, parallelism, sorted))) + ERR_RETURN(trans->getNdbError()); + m_active_cursor= cursor; + } else { + restart= true; + op= (NdbIndexScanOperation*)m_active_cursor->getOperation(); + + DBUG_ASSERT(op->getSorted() == sorted); + DBUG_ASSERT(op->getLockMode() == + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type)); + if(op->reset_bounds()) + DBUG_RETURN(ndb_err(m_active_trans)); + } - if (start_key && - set_bounds(op, start_key, - (start_key->flag == HA_READ_KEY_EXACT) ? - NdbIndexScanOperation::BoundEQ : - (start_key->flag == HA_READ_AFTER_KEY) ? - NdbIndexScanOperation::BoundLT : - NdbIndexScanOperation::BoundLE)) - DBUG_RETURN(1); + { + const key_range *keys[2]= { start_key, end_key }; + int ret= set_bounds(op, keys); + if (ret) + DBUG_RETURN(ret); + } - if (end_key) + if (!restart) { - if (start_key && start_key->flag == HA_READ_KEY_EXACT) - { - DBUG_PRINT("info", ("start_key is HA_READ_KEY_EXACT ignoring end_key")); - } - else if (set_bounds(op, end_key, - (end_key->flag == HA_READ_AFTER_KEY) ? - NdbIndexScanOperation::BoundGE : - NdbIndexScanOperation::BoundGT)) - DBUG_RETURN(1); + DBUG_RETURN(define_read_attrs(buf, op)); + } + else + { + if (execute_no_commit(this,trans) != 0) + DBUG_RETURN(ndb_err(trans)); + + DBUG_RETURN(next_result(buf)); } - DBUG_RETURN(define_read_attrs(buf, op)); } - /* Start a filtered scan in NDB. @@ -1227,11 +1540,10 @@ int ha_ndbcluster::filtered_scan(const byte *key, uint key_len, DBUG_PRINT("info", ("Starting a new filtered scan on %s", m_tabname)); - if (!(op= trans->getNdbScanOperation((NDBTAB *) m_table))) - ERR_RETURN(trans->getNdbError()); - NdbScanOperation::LockMode lm= (NdbScanOperation::LockMode) - get_ndb_lock_type(m_lock.type); - if (!(cursor= op->readTuples(lm, 0, parallelism))) + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); + if (!(op= trans->getNdbScanOperation((const NDBTAB *) m_table)) || + !(cursor= op->readTuples(lm, 0, parallelism))) ERR_RETURN(trans->getNdbError()); m_active_cursor= cursor; @@ -1298,68 +1610,18 @@ int ha_ndbcluster::full_table_scan(byte *buf) DBUG_ENTER("full_table_scan"); DBUG_PRINT("enter", ("Starting new scan on %s", m_tabname)); - if (!(op=trans->getNdbScanOperation((NDBTAB *) m_table))) - ERR_RETURN(trans->getNdbError()); - NdbScanOperation::LockMode lm= (NdbScanOperation::LockMode) - get_ndb_lock_type(m_lock.type); - if (!(cursor= op->readTuples(lm, 0, parallelism))) + NdbOperation::LockMode lm= + (NdbOperation::LockMode)get_ndb_lock_type(m_lock.type); + if (!(op=trans->getNdbScanOperation((const NDBTAB *) m_table)) || + !(cursor= op->readTuples(lm, 0, parallelism))) ERR_RETURN(trans->getNdbError()); m_active_cursor= cursor; DBUG_RETURN(define_read_attrs(buf, op)); } - -inline -int ha_ndbcluster::define_read_attrs(byte* buf, NdbOperation* op) -{ - uint i; - THD *thd= current_thd; - NdbConnection *trans= m_active_trans; - - DBUG_ENTER("define_read_attrs"); - - // Define attributes to read - for (i= 0; i < table->fields; i++) - { - Field *field= table->field[i]; - if ((thd->query_id == field->query_id) || - (field->flags & PRI_KEY_FLAG) || - retrieve_all_fields) - { - if (get_ndb_value(op, field, i)) - ERR_RETURN(op->getNdbError()); - } - else - { - m_value[i].ptr= NULL; - } - } - - if (table->primary_key == MAX_KEY) - { - DBUG_PRINT("info", ("Getting hidden key")); - // Scanning table with no primary key - int hidden_no= table->fields; -#ifndef DBUG_OFF - const NDBTAB *tab= (NDBTAB *) m_table; - if (!tab->getColumn(hidden_no)) - DBUG_RETURN(1); -#endif - if (get_ndb_value(op, NULL, hidden_no)) - ERR_RETURN(op->getNdbError()); - } - - if (trans->execute(NoCommit) != 0) - DBUG_RETURN(ndb_err(trans)); - DBUG_PRINT("exit", ("Scan started successfully")); - DBUG_RETURN(next_result(buf)); -} - - /* Insert one record into NDB */ - int ha_ndbcluster::write_row(byte *record) { bool has_auto_increment; @@ -1370,14 +1632,18 @@ int ha_ndbcluster::write_row(byte *record) THD *thd= current_thd; DBUG_ENTER("write_row"); + + if(m_ignore_dup_key_not_supported) + { + DBUG_RETURN(HA_ERR_WRONG_COMMAND); + } statistic_increment(thd->status_var.ha_write_count, &LOCK_status); - if (table->timestamp_default_now) - update_timestamp(record+table->timestamp_default_now-1); + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) + table->timestamp_field->set_time(); has_auto_increment= (table->next_number_field && record == table->record[0]); - skip_auto_increment= table->auto_increment_field_not_null; - if (!(op= trans->getNdbOperation((NDBTAB *) m_table))) + if (!(op= trans->getNdbOperation((const NDBTAB *) m_table))) ERR_RETURN(trans->getNdbError()); res= (m_use_write) ? op->writeTuple() :op->insertTuple(); @@ -1387,7 +1653,7 @@ int ha_ndbcluster::write_row(byte *record) if (table->primary_key == MAX_KEY) { // Table has hidden primary key - Uint64 auto_value= m_ndb->getAutoIncrementValue((NDBTAB *) m_table); + Uint64 auto_value= m_ndb->getAutoIncrementValue((const NDBTAB *) m_table); if (set_hidden_key(op, table->fields, (const byte*)&auto_value)) ERR_RETURN(op->getNdbError()); } @@ -1395,21 +1661,26 @@ int ha_ndbcluster::write_row(byte *record) { int res; - if ((has_auto_increment) && (!skip_auto_increment)) + if (has_auto_increment) + { + skip_auto_increment= FALSE; update_auto_increment(); + skip_auto_increment= !auto_increment_column_changed; + } if ((res= set_primary_key(op))) return res; } // Set non-key attribute(s) + bool set_blob_value= FALSE; for (i= 0; i < table->fields; i++) { Field *field= table->field[i]; if (!(field->flags & PRI_KEY_FLAG) && - set_ndb_value(op, field, i)) + set_ndb_value(op, field, i, &set_blob_value)) { - skip_auto_increment= true; + skip_auto_increment= TRUE; ERR_RETURN(op->getNdbError()); } } @@ -1422,20 +1693,38 @@ int ha_ndbcluster::write_row(byte *record) Find out how this is detected! */ rows_inserted++; - bulk_insert_not_flushed= true; + no_uncommitted_rows_update(1); + bulk_insert_not_flushed= TRUE; if ((rows_to_insert == 1) || ((rows_inserted % bulk_insert_rows) == 0) || - uses_blob_value(false) != 0) + set_blob_value) { + THD *thd= current_thd; // Send rows to NDB DBUG_PRINT("info", ("Sending inserts to NDB, "\ "rows_inserted:%d, bulk_insert_rows: %d", - (int)rows_inserted, (int)bulk_insert_rows)); - bulk_insert_not_flushed= false; - if (trans->execute(NoCommit) != 0) + (int)rows_inserted, (int)bulk_insert_rows)); + + bulk_insert_not_flushed= FALSE; + if (thd->transaction.on) { - skip_auto_increment= true; - DBUG_RETURN(ndb_err(trans)); + if (execute_no_commit(this,trans) != 0) + { + skip_auto_increment= TRUE; + no_uncommitted_rows_execute_failure(); + DBUG_RETURN(ndb_err(trans)); + } + } + else + { + if (execute_commit(this,trans) != 0) + { + skip_auto_increment= TRUE; + no_uncommitted_rows_execute_failure(); + DBUG_RETURN(ndb_err(trans)); + } + int res= trans->restart(); + DBUG_ASSERT(res == 0); } } if ((has_auto_increment) && (skip_auto_increment)) @@ -1444,11 +1733,11 @@ int ha_ndbcluster::write_row(byte *record) DBUG_PRINT("info", ("Trying to set next auto increment value to %lu", (ulong) next_val)); - if (m_ndb->setAutoIncrementValue((NDBTAB *) m_table, next_val, true)) + if (m_ndb->setAutoIncrementValue((const NDBTAB *) m_table, next_val, TRUE)) DBUG_PRINT("info", ("Setting next auto increment value to %u", next_val)); } - skip_auto_increment= true; + skip_auto_increment= TRUE; DBUG_RETURN(0); } @@ -1502,9 +1791,9 @@ int ha_ndbcluster::update_row(const byte *old_data, byte *new_data) DBUG_ENTER("update_row"); statistic_increment(thd->status_var.ha_update_count, &LOCK_status); - if (table->timestamp_on_update_now) - update_timestamp(new_data+table->timestamp_on_update_now-1); - + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + /* Check for update of primary key for special handling */ if ((table->primary_key != MAX_KEY) && (key_cmp(table->primary_key, old_data, new_data))) @@ -1552,12 +1841,12 @@ int ha_ndbcluster::update_row(const byte *old_data, byte *new_data) if (!(op= cursor->updateTuple())) ERR_RETURN(trans->getNdbError()); ops_pending++; - if (uses_blob_value(false)) - blobs_pending= true; + if (uses_blob_value(FALSE)) + blobs_pending= TRUE; } else { - if (!(op= trans->getNdbOperation((NDBTAB *) m_table)) || + if (!(op= trans->getNdbOperation((const NDBTAB *) m_table)) || op->updateTuple() != 0) ERR_RETURN(trans->getNdbError()); @@ -1595,8 +1884,10 @@ int ha_ndbcluster::update_row(const byte *old_data, byte *new_data) } // Execute update operation - if (!cursor && trans->execute(NoCommit) != 0) + if (!cursor && execute_no_commit(this,trans) != 0) { + no_uncommitted_rows_execute_failure(); DBUG_RETURN(ndb_err(trans)); + } DBUG_RETURN(0); } @@ -1630,16 +1921,20 @@ int ha_ndbcluster::delete_row(const byte *record) ERR_RETURN(trans->getNdbError()); ops_pending++; + no_uncommitted_rows_update(-1); + // If deleting from cursor, NoCommit will be handled in next_result DBUG_RETURN(0); } else { - if (!(op=trans->getNdbOperation((NDBTAB *) m_table)) || + if (!(op=trans->getNdbOperation((const NDBTAB *) m_table)) || op->deleteTuple() != 0) ERR_RETURN(trans->getNdbError()); + no_uncommitted_rows_update(-1); + if (table->primary_key == MAX_KEY) { // This table has no primary key, use "hidden" primary key @@ -1660,8 +1955,10 @@ int ha_ndbcluster::delete_row(const byte *record) } // Execute delete operation - if (trans->execute(NoCommit) != 0) + if (execute_no_commit(this,trans) != 0) { + no_uncommitted_rows_execute_failure(); DBUG_RETURN(ndb_err(trans)); + } DBUG_RETURN(0); } @@ -1702,7 +1999,7 @@ void ha_ndbcluster::unpack_record(byte* buf) else { NdbBlob* ndb_blob= (*value).blob; - bool isNull= true; + bool isNull= TRUE; int ret= ndb_blob->getNull(isNull); DBUG_ASSERT(ret == 0); if (isNull) @@ -1717,7 +2014,7 @@ void ha_ndbcluster::unpack_record(byte* buf) { // Table with hidden primary key int hidden_no= table->fields; - const NDBTAB *tab= (NDBTAB *) m_table; + const NDBTAB *tab= (const NDBTAB *) m_table; const NDBCOL *hidden_col= tab->getColumn(hidden_no); NdbRecAttr* rec= m_value[hidden_no].rec; DBUG_ASSERT(rec); @@ -1735,7 +2032,7 @@ void ha_ndbcluster::unpack_record(byte* buf) void ha_ndbcluster::print_results() { - const NDBTAB *tab= (NDBTAB*) m_table; + const NDBTAB *tab= (const NDBTAB*) m_table; DBUG_ENTER("print_results"); #ifndef DBUG_OFF @@ -1770,7 +2067,7 @@ void ha_ndbcluster::print_results() else { ndb_blob= value.blob; - bool isNull= true; + bool isNull= TRUE; ndb_blob->getNull(isNull); if (isNull) { fprintf(DBUG_FILE, "NULL\n"); @@ -1910,18 +2207,54 @@ int ha_ndbcluster::index_end() int ha_ndbcluster::index_read(byte *buf, - const byte *key, uint key_len, - enum ha_rkey_function find_flag) + const byte *key, uint key_len, + enum ha_rkey_function find_flag) { DBUG_ENTER("index_read"); DBUG_PRINT("enter", ("active_index: %u, key_len: %u, find_flag: %d", active_index, key_len, find_flag)); + int error; + ndb_index_type type = get_index_type(active_index); + const KEY* key_info = table->key_info+active_index; + switch (type){ + case PRIMARY_KEY_ORDERED_INDEX: + case PRIMARY_KEY_INDEX: + if (find_flag == HA_READ_KEY_EXACT && key_info->key_length == key_len) + { + DBUG_RETURN(pk_read(key, key_len, buf)); + } + else if (type == PRIMARY_KEY_INDEX) + { + DBUG_RETURN(1); + } + break; + case UNIQUE_ORDERED_INDEX: + case UNIQUE_INDEX: + if (find_flag == HA_READ_KEY_EXACT && key_info->key_length == key_len) + { + DBUG_RETURN(unique_index_read(key, key_len, buf)); + } + else if (type == UNIQUE_INDEX) + { + DBUG_RETURN(1); + } + break; + case ORDERED_INDEX: + break; + default: + case UNDEFINED_INDEX: + DBUG_ASSERT(FALSE); + DBUG_RETURN(1); + break; + } + key_range start_key; - start_key.key= key; - start_key.length= key_len; - start_key.flag= find_flag; - DBUG_RETURN(read_range_first(&start_key, NULL, false, true)); + start_key.key = key; + start_key.length = key_len; + start_key.flag = find_flag; + error= ordered_index_scan(&start_key, 0, TRUE, buf); + DBUG_RETURN(error == HA_ERR_END_OF_FILE ? HA_ERR_KEY_NOT_FOUND : error); } @@ -1962,7 +2295,10 @@ int ha_ndbcluster::index_first(byte *buf) DBUG_ENTER("index_first"); statistic_increment(current_thd->status_var.ha_read_first_count, &LOCK_status); - DBUG_RETURN(1); + // Start the ordered index scan and fetch the first row + + // Only HA_READ_ORDER indexes get called by index_first + DBUG_RETURN(ordered_index_scan(0, 0, TRUE, buf)); } @@ -1970,23 +2306,31 @@ int ha_ndbcluster::index_last(byte *buf) { DBUG_ENTER("index_last"); statistic_increment(current_thd->status_var.ha_read_last_count,&LOCK_status); + int res; + if((res= ordered_index_scan(0, 0, TRUE, buf)) == 0){ + NdbResultSet *cursor= m_active_cursor; + while((res= cursor->nextResult(TRUE)) == 0); + if(res == 1){ + unpack_record(buf); + table->status= 0; + DBUG_RETURN(0); + } + } DBUG_RETURN(1); } -int ha_ndbcluster::read_range_first(const key_range *start_key, - const key_range *end_key, - bool eq_range, bool sorted) +inline +int ha_ndbcluster::read_range_first_to_buf(const key_range *start_key, + const key_range *end_key, + bool eq_range, bool sorted, + byte* buf) { KEY* key_info; int error= 1; - byte* buf= table->record[0]; - DBUG_ENTER("ha_ndbcluster::read_range_first"); + DBUG_ENTER("ha_ndbcluster::read_range_first_to_buf"); DBUG_PRINT("info", ("eq_range: %d, sorted: %d", eq_range, sorted)); - if (m_active_cursor) - close_scan(); - switch (get_index_type(active_index)){ case PRIMARY_KEY_ORDERED_INDEX: case PRIMARY_KEY_INDEX: @@ -2014,15 +2358,26 @@ int ha_ndbcluster::read_range_first(const key_range *start_key, break; } - // Start the ordered index scan and fetch the first row - error= ordered_index_scan(start_key, end_key, sorted, - buf); - + error= ordered_index_scan(start_key, end_key, sorted, buf); DBUG_RETURN(error); } +int ha_ndbcluster::read_range_first(const key_range *start_key, + const key_range *end_key, + bool eq_range, bool sorted) +{ + byte* buf= table->record[0]; + DBUG_ENTER("ha_ndbcluster::read_range_first"); + + DBUG_RETURN(read_range_first_to_buf(start_key, + end_key, + eq_range, + sorted, + buf)); +} + int ha_ndbcluster::read_range_next() { DBUG_ENTER("ha_ndbcluster::read_range_next"); @@ -2040,7 +2395,8 @@ int ha_ndbcluster::rnd_init(bool scan) { if (!scan) DBUG_RETURN(1); - cursor->restart(); + int res= cursor->restart(); + DBUG_ASSERT(res == 0); } index_init(table->primary_key); DBUG_RETURN(0); @@ -2063,8 +2419,10 @@ int ha_ndbcluster::close_scan() deleteing/updating transaction before closing the scan */ DBUG_PRINT("info", ("ops_pending: %d", ops_pending)); - if (trans->execute(NoCommit) != 0) + if (execute_no_commit(this,trans) != 0) { + no_uncommitted_rows_execute_failure(); DBUG_RETURN(ndb_err(trans)); + } ops_pending= 0; } @@ -2152,7 +2510,7 @@ void ha_ndbcluster::position(const byte *record) DBUG_PRINT("info", ("Getting hidden key")); int hidden_no= table->fields; NdbRecAttr* rec= m_value[hidden_no].rec; - const NDBTAB *tab= (NDBTAB *) m_table; + const NDBTAB *tab= (const NDBTAB *) m_table; const NDBCOL *hidden_col= tab->getColumn(hidden_no); DBUG_ASSERT(hidden_col->getPrimaryKey() && hidden_col->getAutoIncrement() && @@ -2177,10 +2535,26 @@ void ha_ndbcluster::info(uint flag) DBUG_PRINT("info", ("HA_STATUS_NO_LOCK")); if (flag & HA_STATUS_TIME) DBUG_PRINT("info", ("HA_STATUS_TIME")); - if (flag & HA_STATUS_CONST) - DBUG_PRINT("info", ("HA_STATUS_CONST")); if (flag & HA_STATUS_VARIABLE) + { DBUG_PRINT("info", ("HA_STATUS_VARIABLE")); + if (m_table_info) + { + records_update(); + } + else + { + Uint64 rows; + if(ndb_get_table_statistics(m_ndb, m_tabname, &rows, 0) == 0){ + records= rows; + } + } + } + if (flag & HA_STATUS_CONST) + { + DBUG_PRINT("info", ("HA_STATUS_CONST")); + set_rec_per_key(); + } if (flag & HA_STATUS_ERRKEY) { DBUG_PRINT("info", ("HA_STATUS_ERRKEY")); @@ -2273,14 +2647,20 @@ int ha_ndbcluster::extra(enum ha_extra_function operation) break; case HA_EXTRA_IGNORE_DUP_KEY: /* Dup keys don't rollback everything*/ DBUG_PRINT("info", ("HA_EXTRA_IGNORE_DUP_KEY")); - - DBUG_PRINT("info", ("Turning ON use of write instead of insert")); - m_use_write= TRUE; + if (current_thd->lex->sql_command == SQLCOM_REPLACE) + { + DBUG_PRINT("info", ("Turning ON use of write instead of insert")); + m_use_write= TRUE; + } else + { + m_ignore_dup_key_not_supported= TRUE; + } break; case HA_EXTRA_NO_IGNORE_DUP_KEY: DBUG_PRINT("info", ("HA_EXTRA_NO_IGNORE_DUP_KEY")); DBUG_PRINT("info", ("Turning OFF use of write instead of insert")); - m_use_write= false; + m_use_write= FALSE; + m_ignore_dup_key_not_supported= FALSE; break; case HA_EXTRA_RETRIEVE_ALL_COLS: /* Retrieve all columns, not just those where field->query_id is the same as @@ -2327,7 +2707,7 @@ int ha_ndbcluster::extra(enum ha_extra_function operation) void ha_ndbcluster::start_bulk_insert(ha_rows rows) { int bytes, batch; - const NDBTAB *tab= (NDBTAB *) m_table; + const NDBTAB *tab= (const NDBTAB *) m_table; DBUG_ENTER("start_bulk_insert"); DBUG_PRINT("enter", ("rows: %d", (int)rows)); @@ -2368,9 +2748,11 @@ int ha_ndbcluster::end_bulk_insert() DBUG_PRINT("info", ("Sending inserts to NDB, "\ "rows_inserted:%d, bulk_insert_rows: %d", rows_inserted, bulk_insert_rows)); - bulk_insert_not_flushed= false; - if (trans->execute(NoCommit) != 0) - error= ndb_err(trans); + bulk_insert_not_flushed= FALSE; + if (execute_no_commit(this,trans) != 0) { + no_uncommitted_rows_execute_failure(); + my_errno= error= ndb_err(trans); + } } rows_inserted= 0; @@ -2396,7 +2778,7 @@ int ha_ndbcluster::reset() const char **ha_ndbcluster::bas_ext() const -{ static const char *ext[1]= { NullS }; return ext; } +{ static const char *ext[]= { ".ndb", NullS }; return ext; } /* @@ -2407,7 +2789,11 @@ const char **ha_ndbcluster::bas_ext() const double ha_ndbcluster::scan_time() { - return rows2double(records*1000); + DBUG_ENTER("ha_ndbcluster::scan_time()"); + double res= rows2double(records*1000); + DBUG_PRINT("exit", ("table: %s value: %f", + m_tabname, res)); + DBUG_RETURN(res); } @@ -2416,14 +2802,16 @@ THR_LOCK_DATA **ha_ndbcluster::store_lock(THD *thd, enum thr_lock_type lock_type) { DBUG_ENTER("store_lock"); - if (lock_type != TL_IGNORE && m_lock.type == TL_UNLOCK) { - + /* If we are not doing a LOCK TABLE, then allow multiple writers */ - if ((lock_type >= TL_WRITE_CONCURRENT_INSERT && + /* Since NDB does not currently have table locks + this is treated as a ordinary lock */ + + if ((lock_type >= TL_WRITE_ALLOW_WRITE && lock_type <= TL_WRITE) && !thd->in_lock_tables) lock_type= TL_WRITE_ALLOW_WRITE; @@ -2477,9 +2865,6 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) NdbConnection* trans= NULL; DBUG_ENTER("external_lock"); - DBUG_PRINT("enter", ("transaction.ndb_lock_count: %d", - thd->transaction.ndb_lock_count)); - /* Check that this handler instance has a connection set up to the Ndb object of thd @@ -2487,10 +2872,15 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) if (check_ndb_connection()) DBUG_RETURN(1); + Thd_ndb *thd_ndb= (Thd_ndb*)thd->transaction.thd_ndb; + + DBUG_PRINT("enter", ("transaction.thd_ndb->lock_count: %d", + thd_ndb->lock_count)); + if (lock_type != F_UNLCK) { DBUG_PRINT("info", ("lock_type != F_UNLCK")); - if (!thd->transaction.ndb_lock_count++) + if (!thd_ndb->lock_count++) { PRINT_OPTION_FLAGS(thd); @@ -2502,10 +2892,8 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) trans= m_ndb->startTransaction(); if (trans == NULL) - { - thd->transaction.ndb_lock_count--; // We didn't get the lock ERR_RETURN(m_ndb->getNdbError()); - } + no_uncommitted_rows_reset(thd); thd->transaction.stmt.ndb_tid= trans; } else @@ -2518,10 +2906,8 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) trans= m_ndb->startTransaction(); if (trans == NULL) - { - thd->transaction.ndb_lock_count--; // We didn't get the lock ERR_RETURN(m_ndb->getNdbError()); - } + no_uncommitted_rows_reset(thd); /* If this is the start of a LOCK TABLE, a table look @@ -2555,15 +2941,25 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) (NdbConnection*)thd->transaction.all.ndb_tid: (NdbConnection*)thd->transaction.stmt.ndb_tid; DBUG_ASSERT(m_active_trans); - // Start of transaction retrieve_all_fields= FALSE; ops_pending= 0; + { + NDBDICT *dict= m_ndb->getDictionary(); + const NDBTAB *tab; + void *tab_info; + if (!(tab= dict->getTable(m_tabname, &tab_info))) + ERR_RETURN(dict->getNdbError()); + DBUG_PRINT("info", ("Table schema version: %d", tab->getObjectVersion())); + m_table= (void *)tab; + m_table_info= tab_info; + } + no_uncommitted_rows_init(thd); } else { DBUG_PRINT("info", ("lock_type == F_UNLCK")); - if (!--thd->transaction.ndb_lock_count) + if (!--thd_ndb->lock_count) { DBUG_PRINT("trans", ("Last external_lock")); PRINT_OPTION_FLAGS(thd); @@ -2580,7 +2976,28 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) thd->transaction.stmt.ndb_tid= 0; } } - m_active_trans= NULL; + m_table= NULL; + m_table_info= NULL; + /* + This is the place to make sure this handler instance + no longer are connected to the active transaction. + + And since the handler is no longer part of the transaction + it can't have open cursors, ops or blobs pending. + */ + m_active_trans= NULL; + + if (m_active_cursor) + DBUG_PRINT("warning", ("m_active_cursor != NULL")); + m_active_cursor= NULL; + + if (blobs_pending) + DBUG_PRINT("warning", ("blobs_pending != 0")); + blobs_pending= 0; + + if (ops_pending) + DBUG_PRINT("warning", ("ops_pending != 0L")); + ops_pending= 0; } DBUG_RETURN(error); } @@ -2589,6 +3006,8 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) When using LOCK TABLE's external_lock is only called when the actual TABLE LOCK is done. Under LOCK TABLES, each used tables will force a call to start_stmt. + Ndb doesn't currently support table locks, and will do ordinary + startTransaction for each transaction/statement. */ int ha_ndbcluster::start_stmt(THD *thd) @@ -2604,9 +3023,12 @@ int ha_ndbcluster::start_stmt(THD *thd) NdbConnection *tablock_trans= (NdbConnection*)thd->transaction.all.ndb_tid; DBUG_PRINT("info", ("tablock_trans: %x", (uint)tablock_trans)); - DBUG_ASSERT(tablock_trans); trans= m_ndb->hupp(tablock_trans); + DBUG_ASSERT(tablock_trans); +// trans= m_ndb->hupp(tablock_trans); + trans= m_ndb->startTransaction(); if (trans == NULL) ERR_RETURN(m_ndb->getNdbError()); + no_uncommitted_rows_reset(thd); thd->transaction.stmt.ndb_tid= trans; } m_active_trans= trans; @@ -2626,7 +3048,7 @@ int ha_ndbcluster::start_stmt(THD *thd) int ndbcluster_commit(THD *thd, void *ndb_transaction) { int res= 0; - Ndb *ndb= (Ndb*)thd->transaction.ndb; + Ndb *ndb= ((Thd_ndb*)thd->transaction.thd_ndb)->ndb; NdbConnection *trans= (NdbConnection*)ndb_transaction; DBUG_ENTER("ndbcluster_commit"); @@ -2635,7 +3057,7 @@ int ndbcluster_commit(THD *thd, void *ndb_transaction) "stmt" : "all")); DBUG_ASSERT(ndb && trans); - if (trans->execute(Commit) != 0) + if (execute_commit(0,trans) != 0) { const NdbError err= trans->getNdbError(); const NdbOperation *error_op= trans->getNdbErrorOperation(); @@ -2644,7 +3066,7 @@ int ndbcluster_commit(THD *thd, void *ndb_transaction) if (res != -1) ndbcluster_print_error(res, error_op); } - ndb->closeTransaction(trans); + ndb->closeTransaction(trans); DBUG_RETURN(res); } @@ -2656,7 +3078,7 @@ int ndbcluster_commit(THD *thd, void *ndb_transaction) int ndbcluster_rollback(THD *thd, void *ndb_transaction) { int res= 0; - Ndb *ndb= (Ndb*)thd->transaction.ndb; + Ndb *ndb= ((Thd_ndb*)thd->transaction.thd_ndb)->ndb; NdbConnection *trans= (NdbConnection*)ndb_transaction; DBUG_ENTER("ndbcluster_rollback"); @@ -2691,6 +3113,8 @@ static int create_ndb_column(NDBCOL &col, { // Set name col.setName(field->field_name); + // Get char set + CHARSET_INFO *cs= field->charset(); // Set type and sizes const enum enum_field_types mysql_type= field->real_type(); switch (mysql_type) { @@ -2762,15 +3186,22 @@ static int create_ndb_column(NDBCOL &col, case MYSQL_TYPE_STRING: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Binary); - else + else { col.setType(NDBCOL::Char); - col.setLength(field->pack_length()); + col.setCharset(cs); + } + if (field->pack_length() == 0) + col.setLength(1); // currently ndb does not support size 0 + else + col.setLength(field->pack_length()); break; case MYSQL_TYPE_VAR_STRING: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Varbinary); - else + else { col.setType(NDBCOL::Varchar); + col.setCharset(cs); + } col.setLength(field->pack_length()); break; // Blob types (all come in as MYSQL_TYPE_BLOB) @@ -2778,8 +3209,10 @@ static int create_ndb_column(NDBCOL &col, case MYSQL_TYPE_TINY_BLOB: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Blob); - else + else { col.setType(NDBCOL::Text); + col.setCharset(cs); + } col.setInlineSize(256); // No parts col.setPartSize(0); @@ -2789,8 +3222,10 @@ static int create_ndb_column(NDBCOL &col, case MYSQL_TYPE_BLOB: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Blob); - else + else { col.setType(NDBCOL::Text); + col.setCharset(cs); + } // Use "<=" even if "<" is the exact condition if (field->max_length() <= (1 << 8)) goto mysql_type_tiny_blob; @@ -2809,8 +3244,10 @@ static int create_ndb_column(NDBCOL &col, case MYSQL_TYPE_MEDIUM_BLOB: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Blob); - else + else { col.setType(NDBCOL::Text); + col.setCharset(cs); + } col.setInlineSize(256); col.setPartSize(4000); col.setStripeSize(8); @@ -2819,8 +3256,10 @@ static int create_ndb_column(NDBCOL &col, case MYSQL_TYPE_LONG_BLOB: if (field->flags & BINARY_FLAG) col.setType(NDBCOL::Blob); - else + else { col.setType(NDBCOL::Text); + col.setCharset(cs); + } col.setInlineSize(256); col.setPartSize(8000); col.setStripeSize(4); @@ -2849,12 +3288,12 @@ static int create_ndb_column(NDBCOL &col, { col.setAutoIncrement(TRUE); ulonglong value= info->auto_increment_value ? - info->auto_increment_value -1 : (ulonglong) 0; + info->auto_increment_value : (ulonglong) 1; DBUG_PRINT("info", ("Autoincrement key, initial: %llu", value)); col.setAutoIncrementInitialValue(value); } else - col.setAutoIncrement(false); + col.setAutoIncrement(FALSE); return 0; } @@ -2872,12 +3311,24 @@ int ha_ndbcluster::create(const char *name, const void *data, *pack_data; const char **key_names= form->keynames.type_names; char name2[FN_HEADLEN]; + bool create_from_engine= (info->table_options & HA_CREATE_FROM_ENGINE); DBUG_ENTER("create"); DBUG_PRINT("enter", ("name: %s", name)); fn_format(name2, name, "", "",2); // Remove the .frm extension set_dbname(name2); - set_tabname(name2); + set_tabname(name2); + + if (create_from_engine) + { + /* + Table alreay exists in NDB and frm file has been created by + caller. + Do Ndb specific stuff, such as create a .ndb file + */ + my_errno= write_ndb_file(); + DBUG_RETURN(my_errno); + } DBUG_PRINT("table", ("name: %s", m_tabname)); tab.setName(m_tabname); @@ -2912,22 +3363,18 @@ int ha_ndbcluster::create(const char *name, col.setName("$PK"); col.setType(NdbDictionary::Column::Bigunsigned); col.setLength(1); - col.setNullable(false); + col.setNullable(FALSE); col.setPrimaryKey(TRUE); col.setAutoIncrement(TRUE); tab.addColumn(col); } - my_errno= 0; - if (check_ndb_connection()) - { - my_errno= HA_ERR_NO_CONNECTION; + if ((my_errno= check_ndb_connection())) DBUG_RETURN(my_errno); - } // Create the table in NDB NDBDICT *dict= m_ndb->getDictionary(); - if (dict->createTable(tab)) + if (dict->createTable(tab) != 0) { const NdbError err= dict->getNdbError(); ERR_PRINT(err); @@ -2940,6 +3387,9 @@ int ha_ndbcluster::create(const char *name, // Create secondary indexes my_errno= build_index_list(form, ILBP_CREATE); + if (!my_errno) + my_errno= write_ndb_file(); + DBUG_RETURN(my_errno); } @@ -2948,7 +3398,7 @@ int ha_ndbcluster::create_ordered_index(const char *name, KEY *key_info) { DBUG_ENTER("create_ordered_index"); - DBUG_RETURN(create_index(name, key_info, false)); + DBUG_RETURN(create_index(name, key_info, FALSE)); } int ha_ndbcluster::create_unique_index(const char *name, @@ -2956,7 +3406,7 @@ int ha_ndbcluster::create_unique_index(const char *name, { DBUG_ENTER("create_unique_index"); - DBUG_RETURN(create_index(name, key_info, true)); + DBUG_RETURN(create_index(name, key_info, TRUE)); } @@ -2975,7 +3425,6 @@ int ha_ndbcluster::create_index(const char *name, DBUG_ENTER("create_index"); DBUG_PRINT("enter", ("name: %s ", name)); - // NdbDictionary::Index ndb_index(name); NdbDictionary::Index ndb_index(name); if (unique) ndb_index.setType(NdbDictionary::Index::UniqueHashIndex); @@ -2983,7 +3432,7 @@ int ha_ndbcluster::create_index(const char *name, { ndb_index.setType(NdbDictionary::Index::OrderedIndex); // TODO Only temporary ordered indexes supported - ndb_index.setLogging(false); + ndb_index.setLogging(FALSE); } ndb_index.setTable(m_tabname); @@ -3016,14 +3465,16 @@ int ha_ndbcluster::rename_table(const char *from, const char *to) set_tabname(from); set_tabname(to, new_tabname); - if (check_ndb_connection()) { - my_errno= HA_ERR_NO_CONNECTION; - DBUG_RETURN(my_errno); - } + if (check_ndb_connection()) + DBUG_RETURN(my_errno= HA_ERR_NO_CONNECTION); + int result= alter_table_name(m_tabname, new_tabname); if (result == 0) + { set_tabname(to); + handler::rename_table(from, to); + } DBUG_RETURN(result); } @@ -3049,6 +3500,7 @@ int ha_ndbcluster::alter_table_name(const char *from, const char *to) ERR_RETURN(dict->getNdbError()); m_table= NULL; + m_table_info= NULL; DBUG_RETURN(0); } @@ -3067,6 +3519,8 @@ int ha_ndbcluster::delete_table(const char *name) if (check_ndb_connection()) DBUG_RETURN(HA_ERR_NO_CONNECTION); + + handler::delete_table(name); DBUG_RETURN(drop_table()); } @@ -3113,11 +3567,12 @@ ulonglong ha_ndbcluster::get_auto_increment() Uint64 auto_value; DBUG_ENTER("get_auto_increment"); DBUG_PRINT("enter", ("m_tabname: %s", m_tabname)); - cache_size= ((rows_to_insert > autoincrement_prefetch) ? - rows_to_insert : autoincrement_prefetch); + cache_size= ((rows_to_insert - rows_inserted < autoincrement_prefetch) ? + rows_to_insert - rows_inserted : + max(rows_to_insert, autoincrement_prefetch)); auto_value= ((skip_auto_increment) ? - m_ndb->readAutoIncrementValue((NDBTAB *) m_table) : - m_ndb->getAutoIncrementValue((NDBTAB *) m_table, cache_size)); + m_ndb->readAutoIncrementValue((const NDBTAB *) m_table) : + m_ndb->getAutoIncrementValue((const NDBTAB *) m_table, cache_size)); DBUG_RETURN((ulonglong) auto_value); } @@ -3132,18 +3587,22 @@ ha_ndbcluster::ha_ndbcluster(TABLE *table_arg): m_active_cursor(NULL), m_ndb(NULL), m_table(NULL), + m_table_info(NULL), m_table_flags(HA_REC_NOT_IN_SEQ | HA_NULL_IN_KEY | - HA_NOT_EXACT_COUNT | - HA_NO_PREFIX_CHAR_KEYS), - m_use_write(false), + HA_AUTO_PART_KEY | + HA_NO_PREFIX_CHAR_KEYS), + m_share(0), + m_use_write(FALSE), + m_ignore_dup_key_not_supported(FALSE), retrieve_all_fields(FALSE), rows_to_insert(1), rows_inserted(0), bulk_insert_rows(1024), - bulk_insert_not_flushed(false), + bulk_insert_not_flushed(FALSE), ops_pending(0), - skip_auto_increment(true), + skip_auto_increment(TRUE), + blobs_pending(0), blobs_buffer(0), blobs_buffer_size(0), dupkey((uint) -1) @@ -3155,15 +3614,12 @@ ha_ndbcluster::ha_ndbcluster(TABLE *table_arg): m_tabname[0]= '\0'; m_dbname[0]= '\0'; - // TODO Adjust number of records and other parameters for proper - // selection of scan/pk access - records= 100; + records= ~(ha_rows)0; // uninitialized block_size= 1024; for (i= 0; i < MAX_KEY; i++) { m_index[i].type= UNDEFINED_INDEX; - m_index[i].unique_name= NULL; m_index[i].unique_index= NULL; m_index[i].index= NULL; } @@ -3180,12 +3636,18 @@ ha_ndbcluster::~ha_ndbcluster() { DBUG_ENTER("~ha_ndbcluster"); + if (m_share) + free_share(m_share); release_metadata(); my_free(blobs_buffer, MYF(MY_ALLOW_ZERO_PTR)); blobs_buffer= 0; // Check for open cursor/transaction + if (m_active_cursor) { + } DBUG_ASSERT(m_active_cursor == NULL); + if (m_active_trans) { + } DBUG_ASSERT(m_active_trans == NULL); DBUG_VOID_RETURN; @@ -3200,6 +3662,7 @@ ha_ndbcluster::~ha_ndbcluster() int ha_ndbcluster::open(const char *name, int mode, uint test_if_locked) { + int res; KEY *key; DBUG_ENTER("open"); DBUG_PRINT("enter", ("name: %s mode: %d test_if_locked: %d", @@ -3222,10 +3685,16 @@ int ha_ndbcluster::open(const char *name, int mode, uint test_if_locked) set_dbname(name); set_tabname(name); - if (check_ndb_connection()) + if (check_ndb_connection()) { + free_share(m_share); m_share= 0; DBUG_RETURN(HA_ERR_NO_CONNECTION); + } + + res= get_metadata(name); + if (!res) + info(HA_STATUS_VARIABLE | HA_STATUS_CONST); - DBUG_RETURN(get_metadata(name)); + DBUG_RETURN(res); } @@ -3237,89 +3706,87 @@ int ha_ndbcluster::open(const char *name, int mode, uint test_if_locked) int ha_ndbcluster::close(void) { DBUG_ENTER("close"); - free_share(m_share); + free_share(m_share); m_share= 0; release_metadata(); m_ndb= NULL; DBUG_RETURN(0); } -Ndb* ha_ndbcluster::seize_ndb() +Thd_ndb* ha_ndbcluster::seize_thd_ndb() { - Ndb* ndb; - DBUG_ENTER("seize_ndb"); + Thd_ndb *thd_ndb; + DBUG_ENTER("seize_thd_ndb"); -#ifdef USE_NDB_POOL - // Seize from pool - ndb= Ndb::seize(); -#else - ndb= new Ndb(""); -#endif - if (ndb->init(max_transactions) != 0) + thd_ndb= new Thd_ndb(); + thd_ndb->ndb->getDictionary()->set_local_table_data_size(sizeof(Ndb_table_local_info)); + if (thd_ndb->ndb->init(max_transactions) != 0) { - ERR_PRINT(ndb->getNdbError()); + ERR_PRINT(thd_ndb->ndb->getNdbError()); /* TODO Alt.1 If init fails because to many allocated Ndb wait on condition for a Ndb object to be released. Alt.2 Seize/release from pool, wait until next release */ - delete ndb; - ndb= NULL; + delete thd_ndb; + thd_ndb= NULL; } - DBUG_RETURN(ndb); + DBUG_RETURN(thd_ndb); } -void ha_ndbcluster::release_ndb(Ndb* ndb) +void ha_ndbcluster::release_thd_ndb(Thd_ndb* thd_ndb) { - DBUG_ENTER("release_ndb"); -#ifdef USE_NDB_POOL - // Release to pool - Ndb::release(ndb); -#else - delete ndb; -#endif + DBUG_ENTER("release_thd_ndb"); + delete thd_ndb; DBUG_VOID_RETURN; } /* - If this thread already has a Ndb object allocated + If this thread already has a Thd_ndb object allocated in current THD, reuse it. Otherwise - seize a Ndb object, assign it to current THD and use it. - - Having a Ndb object also means that a connection to - NDB cluster has been opened. The connection is - checked. + seize a Thd_ndb object, assign it to current THD and use it. */ +Ndb* check_ndb_in_thd(THD* thd) +{ + DBUG_ENTER("check_ndb_in_thd"); + Thd_ndb *thd_ndb= (Thd_ndb*)thd->transaction.thd_ndb; + + if (!thd_ndb) + { + if (!(thd_ndb= ha_ndbcluster::seize_thd_ndb())) + DBUG_RETURN(NULL); + thd->transaction.thd_ndb= thd_ndb; + } + DBUG_RETURN(thd_ndb->ndb); +} + + int ha_ndbcluster::check_ndb_connection() { THD* thd= current_thd; - Ndb* ndb; DBUG_ENTER("check_ndb_connection"); - if (!thd->transaction.ndb) - { - ndb= seize_ndb(); - if (!ndb) - DBUG_RETURN(2); - thd->transaction.ndb= ndb; - } - m_ndb= (Ndb*)thd->transaction.ndb; + if (!(m_ndb= check_ndb_in_thd(thd))) + DBUG_RETURN(HA_ERR_NO_CONNECTION); m_ndb->setDatabaseName(m_dbname); DBUG_RETURN(0); } + void ndbcluster_close_connection(THD *thd) { - Ndb* ndb; + Thd_ndb *thd_ndb= (Thd_ndb*)thd->transaction.thd_ndb; DBUG_ENTER("ndbcluster_close_connection"); - ndb= (Ndb*)thd->transaction.ndb; - ha_ndbcluster::release_ndb(ndb); - thd->transaction.ndb= NULL; + if (thd_ndb) + { + ha_ndbcluster::release_thd_ndb(thd_ndb); + thd->transaction.thd_ndb= NULL; + } DBUG_VOID_RETURN; } @@ -3328,23 +3795,29 @@ void ndbcluster_close_connection(THD *thd) Try to discover one table from NDB */ -int ndbcluster_discover(const char *dbname, const char *name, +int ndbcluster_discover(THD* thd, const char *db, const char *name, const void** frmblob, uint* frmlen) { uint len; const void* data; const NDBTAB* tab; + Ndb* ndb; DBUG_ENTER("ndbcluster_discover"); - DBUG_PRINT("enter", ("db: %s, name: %s", dbname, name)); + DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); - Ndb ndb(dbname); - if ((ndb.init() != 0) && (ndb.waitUntilReady() != 0)) - ERR_RETURN(ndb.getNdbError()); - - if (!(tab= ndb.getDictionary()->getTable(name))) - { - DBUG_PRINT("info", ("Table %s not found", name)); - DBUG_RETURN(1); + if (!(ndb= check_ndb_in_thd(thd))) + DBUG_RETURN(HA_ERR_NO_CONNECTION); + ndb->setDatabaseName(db); + + NDBDICT* dict= ndb->getDictionary(); + dict->set_local_table_data_size(sizeof(Ndb_table_local_info)); + dict->invalidateTable(name); + if (!(tab= dict->getTable(name))) + { + const NdbError err= dict->getNdbError(); + if (err.code == 709) + DBUG_RETURN(1); + ERR_RETURN(err); } DBUG_PRINT("info", ("Found table %s", tab->getName())); @@ -3366,41 +3839,202 @@ int ndbcluster_discover(const char *dbname, const char *name, DBUG_RETURN(0); } - -#ifdef USE_DISCOVER_ON_STARTUP /* - Dicover tables from NDB Cluster - - fetch a list of tables from NDB - - store the frm file for each table on disk - - if the table has an attached frm file - - if the database of the table exists -*/ + Check if a table exists in NDB + + */ -int ndb_discover_tables() +int ndbcluster_table_exists(THD* thd, const char *db, const char *name) { + uint len; + const void* data; + const NDBTAB* tab; + Ndb* ndb; + DBUG_ENTER("ndbcluster_table_exists"); + DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); + + if (!(ndb= check_ndb_in_thd(thd))) + DBUG_RETURN(HA_ERR_NO_CONNECTION); + ndb->setDatabaseName(db); + + NDBDICT* dict= ndb->getDictionary(); + dict->set_local_table_data_size(sizeof(Ndb_table_local_info)); + dict->invalidateTable(name); + if (!(tab= dict->getTable(name))) + { + const NdbError err= dict->getNdbError(); + if (err.code == 709) + DBUG_RETURN(0); + ERR_RETURN(err); + } + + DBUG_PRINT("info", ("Found table %s", tab->getName())); + DBUG_RETURN(1); +} + + + +extern "C" byte* tables_get_key(const char *entry, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length= strlen(entry); + return (byte*) entry; +} + + +int ndbcluster_find_files(THD *thd,const char *db,const char *path, + const char *wild, bool dir, List<char> *files) +{ + DBUG_ENTER("ndbcluster_find_files"); + DBUG_PRINT("enter", ("db: %s", db)); + { // extra bracket to avoid gcc 2.95.3 warning uint i; + Ndb* ndb; + char name[FN_REFLEN]; + HASH ndb_tables, ok_tables; NdbDictionary::Dictionary::List list; - NdbDictionary::Dictionary* dict; - char path[FN_REFLEN]; - DBUG_ENTER("ndb_discover_tables"); - - /* List tables in NDB Cluster kernel */ - dict= g_ndb->getDictionary(); + + if (!(ndb= check_ndb_in_thd(thd))) + DBUG_RETURN(HA_ERR_NO_CONNECTION); + + if (dir) + DBUG_RETURN(0); // Discover of databases not yet supported + + // List tables in NDB + NDBDICT *dict= ndb->getDictionary(); if (dict->listObjects(list, NdbDictionary::Object::UserTable) != 0) - ERR_RETURN(g_ndb->getNdbError()); - + ERR_RETURN(dict->getNdbError()); + + if (hash_init(&ndb_tables, system_charset_info,list.count,0,0, + (hash_get_key)tables_get_key,0,0)) + { + DBUG_PRINT("error", ("Failed to init HASH ndb_tables")); + DBUG_RETURN(-1); + } + + if (hash_init(&ok_tables, system_charset_info,32,0,0, + (hash_get_key)tables_get_key,0,0)) + { + DBUG_PRINT("error", ("Failed to init HASH ok_tables")); + hash_free(&ndb_tables); + DBUG_RETURN(-1); + } + for (i= 0 ; i < list.count ; i++) { NdbDictionary::Dictionary::List::Element& t= list.elements[i]; + DBUG_PRINT("info", ("Found %s/%s in NDB", t.database, t.name)); + + // Add only tables that belongs to db + if (my_strcasecmp(system_charset_info, t.database, db)) + continue; - DBUG_PRINT("discover", ("%d: %s/%s", t.id, t.database, t.name)); - if (create_table_from_handler(t.database, t.name, true)) - DBUG_PRINT("info", ("Could not discover %s/%s", t.database, t.name)); + // Apply wildcard to list of tables in NDB + if (wild) + { + if (lower_case_table_names) + { + if (wild_case_compare(files_charset_info, t.name, wild)) + continue; + } + else if (wild_compare(t.name,wild,0)) + continue; + } + DBUG_PRINT("info", ("Inserting %s into ndb_tables hash", t.name)); + my_hash_insert(&ndb_tables, (byte*)thd->strdup(t.name)); } - DBUG_RETURN(0); + + char *file_name; + List_iterator<char> it(*files); + List<char> delete_list; + while ((file_name=it++)) + { + DBUG_PRINT("info", ("%s", file_name)); + if (hash_search(&ndb_tables, file_name, strlen(file_name))) + { + DBUG_PRINT("info", ("%s existed in NDB _and_ on disk ", file_name)); + // File existed in NDB and as frm file, put in ok_tables list + my_hash_insert(&ok_tables, (byte*)file_name); + continue; + } + + // File is not in NDB, check for .ndb file with this name + (void)strxnmov(name, FN_REFLEN, + mysql_data_home,"/",db,"/",file_name,ha_ndb_ext,NullS); + DBUG_PRINT("info", ("Check access for %s", name)); + if (access(name, F_OK)) + { + DBUG_PRINT("info", ("%s did not exist on disk", name)); + // .ndb file did not exist on disk, another table type + continue; + } + + DBUG_PRINT("info", ("%s existed on disk", name)); + // The .ndb file exists on disk, but it's not in list of tables in ndb + // Verify that handler agrees table is gone. + if (ndbcluster_table_exists(thd, db, file_name) == 0) + { + DBUG_PRINT("info", ("NDB says %s does not exists", file_name)); + it.remove(); + // Put in list of tables to remove from disk + delete_list.push_back(thd->strdup(file_name)); + } + } + + // Check for new files to discover + DBUG_PRINT("info", ("Checking for new files to discover")); + List<char> create_list; + for (i= 0 ; i < ndb_tables.records ; i++) + { + file_name= hash_element(&ndb_tables, i); + if (!hash_search(&ok_tables, file_name, strlen(file_name))) + { + DBUG_PRINT("info", ("%s must be discovered", file_name)); + // File is in list of ndb tables and not in ok_tables + // This table need to be created + create_list.push_back(thd->strdup(file_name)); + } + } + + // Lock mutex before deleting and creating frm files + pthread_mutex_lock(&LOCK_open); + + if (!global_read_lock) + { + // Delete old files + List_iterator_fast<char> it3(delete_list); + while ((file_name=it3++)) + { + DBUG_PRINT("info", ("Remove table %s/%s",db, file_name )); + // Delete the table and all related files + TABLE_LIST table_list; + bzero((char*) &table_list,sizeof(table_list)); + table_list.db= (char*) db; + table_list.alias=table_list.real_name=(char*)file_name; + (void)mysql_rm_table_part2(thd, &table_list, + /* if_exists */ TRUE, + /* drop_temporary */ FALSE, + /* dont_log_query*/ TRUE); + } + } + + // Create new files + List_iterator_fast<char> it2(create_list); + while ((file_name=it2++)) + { + DBUG_PRINT("info", ("Table %s need discovery", name)); + if (ha_create_table_from_engine(thd, db, file_name, TRUE) == 0) + files->push_back(thd->strdup(file_name)); + } + + pthread_mutex_unlock(&LOCK_open); + + hash_free(&ok_tables); + hash_free(&ndb_tables); + } // extra bracket to avoid gcc 2.95.3 warning + DBUG_RETURN(0); } -#endif /* @@ -3410,34 +4044,55 @@ int ndb_discover_tables() bool ndbcluster_init() { + int res; DBUG_ENTER("ndbcluster_init"); // Set connectstring if specified if (ndbcluster_connectstring != 0) - { DBUG_PRINT("connectstring", ("%s", ndbcluster_connectstring)); - Ndb::setConnectString(ndbcluster_connectstring); + if ((g_ndb_cluster_connection= + new Ndb_cluster_connection(ndbcluster_connectstring)) == 0) + { + DBUG_PRINT("error",("Ndb_cluster_connection(%s)",ndbcluster_connectstring)); + DBUG_RETURN(TRUE); } + // Create a Ndb object to open the connection to NDB - g_ndb= new Ndb("sys"); + g_ndb= new Ndb(g_ndb_cluster_connection, "sys"); + g_ndb->getDictionary()->set_local_table_data_size(sizeof(Ndb_table_local_info)); if (g_ndb->init() != 0) { ERR_PRINT (g_ndb->getNdbError()); DBUG_RETURN(TRUE); } - if (g_ndb->waitUntilReady() != 0) + + if ((res= g_ndb_cluster_connection->connect(1)) == 0) { - ERR_PRINT (g_ndb->getNdbError()); - DBUG_RETURN(TRUE); + g_ndb->waitUntilReady(10); + } + else if(res == 1) + { + if (g_ndb_cluster_connection->start_connect_thread()) { + DBUG_PRINT("error", ("g_ndb_cluster_connection->start_connect_thread()")); + DBUG_RETURN(TRUE); + } } + else + { + DBUG_ASSERT(res == -1); + DBUG_PRINT("error", ("permanent error")); + DBUG_RETURN(TRUE); + } + (void) hash_init(&ndbcluster_open_tables,system_charset_info,32,0,0, (hash_get_key) ndbcluster_get_key,0,0); pthread_mutex_init(&ndbcluster_mutex,MY_MUTEX_INIT_FAST); + ndbcluster_inited= 1; #ifdef USE_DISCOVER_ON_STARTUP if (ndb_discover_tables() != 0) DBUG_RETURN(TRUE); #endif - DBUG_RETURN(false); + DBUG_RETURN(FALSE); } @@ -3450,15 +4105,15 @@ bool ndbcluster_init() bool ndbcluster_end() { DBUG_ENTER("ndbcluster_end"); - - delete g_ndb; + if(g_ndb) + delete g_ndb; g_ndb= NULL; + if (g_ndb_cluster_connection) + delete g_ndb_cluster_connection; + g_ndb_cluster_connection= NULL; if (!ndbcluster_inited) DBUG_RETURN(0); hash_free(&ndbcluster_open_tables); -#ifdef USE_NDB_POOL - ndb_pool_release(); -#endif pthread_mutex_destroy(&ndbcluster_mutex); ndbcluster_inited= 0; DBUG_RETURN(0); @@ -3584,8 +4239,6 @@ ha_ndbcluster::records_in_range(uint inx, key_range *min_key, NDB_INDEX_TYPE idx_type= get_index_type(inx); DBUG_ENTER("records_in_range"); - DBUG_PRINT("enter", ("inx: %u", inx)); - // Prevent partial read of hash indexes by returning HA_POS_ERROR if ((idx_type == UNIQUE_INDEX || idx_type == PRIMARY_KEY_INDEX) && ((min_key && min_key->length < key_length) || @@ -3760,4 +4413,89 @@ static int unpackfrm(const void **unpack_data, uint *unpack_len, DBUG_RETURN(0); } + +static +int +ndb_get_table_statistics(Ndb* ndb, const char * table, + Uint64* row_count, Uint64* commit_count) +{ + DBUG_ENTER("ndb_get_table_statistics"); + DBUG_PRINT("enter", ("table: %s", table)); + + do + { + NdbConnection* pTrans= ndb->startTransaction(); + if (pTrans == NULL) + break; + + NdbScanOperation* pOp= pTrans->getNdbScanOperation(table); + if (pOp == NULL) + break; + + NdbResultSet* rs= pOp->readTuples(NdbOperation::LM_CommittedRead); + if (rs == 0) + break; + + int check= pOp->interpret_exit_last_row(); + if (check == -1) + break; + + Uint64 rows, commits; + pOp->getValue(NdbDictionary::Column::ROW_COUNT, (char*)&rows); + pOp->getValue(NdbDictionary::Column::COMMIT_COUNT, (char*)&commits); + + check= pTrans->execute(NoCommit); + if (check == -1) + break; + + Uint64 sum_rows= 0; + Uint64 sum_commits= 0; + while((check= rs->nextResult(TRUE)) == 0) + { + sum_rows+= rows; + sum_commits+= commits; + } + + if (check == -1) + break; + + ndb->closeTransaction(pTrans); + if(row_count) + * row_count= sum_rows; + if(commit_count) + * commit_count= sum_commits; + DBUG_PRINT("exit", ("records: %u commits: %u", sum_rows, sum_commits)); + DBUG_RETURN(0); + } while(0); + + DBUG_PRINT("exit", ("failed")); + DBUG_RETURN(-1); +} + +/* + Create a .ndb file to serve as a placeholder indicating + that the table with this name is a ndb table +*/ + +int ha_ndbcluster::write_ndb_file() +{ + File file; + bool error=1; + char path[FN_REFLEN]; + + DBUG_ENTER("write_ndb_file"); + DBUG_PRINT("enter", ("db: %s, name: %s", m_dbname, m_tabname)); + + (void)strxnmov(path, FN_REFLEN, + mysql_data_home,"/",m_dbname,"/",m_tabname,ha_ndb_ext,NullS); + + if ((file=my_create(path, CREATE_MODE,O_RDWR | O_TRUNC,MYF(MY_WME))) >= 0) + { + // It's an empty file + error=0; + my_close(file,MYF(0)); + } + DBUG_RETURN(error); +} + #endif /* HAVE_NDBCLUSTER_DB */ diff --git a/sql/ha_ndbcluster.h b/sql/ha_ndbcluster.h index 777e234f935..245d906c5ae 100644 --- a/sql/ha_ndbcluster.h +++ b/sql/ha_ndbcluster.h @@ -52,7 +52,6 @@ typedef enum ndb_index_type { typedef struct ndb_index_data { NDB_INDEX_TYPE type; void *index; - const char * unique_name; void *unique_index; } NDB_INDEX_DATA; @@ -63,6 +62,20 @@ typedef struct st_ndbcluster_share { uint table_name_length,use_count; } NDB_SHARE; +/* + Place holder for ha_ndbcluster thread specific data +*/ + +class Thd_ndb { + public: + Thd_ndb(); + ~Thd_ndb(); + Ndb *ndb; + ulong count; + uint lock_count; + int error; +}; + class ha_ndbcluster: public handler { public: @@ -93,6 +106,10 @@ class ha_ndbcluster: public handler int read_range_first(const key_range *start_key, const key_range *end_key, bool eq_range, bool sorted); + int read_range_first_to_buf(const key_range *start_key, + const key_range *end_key, + bool eq_range, bool sorted, + byte* buf); int read_range_next(); bool get_error_message(int error, String *buf); @@ -122,12 +139,12 @@ class ha_ndbcluster: public handler bool low_byte_first() const { #ifdef WORDS_BIGENDIAN - return false; + return FALSE; #else - return true; + return TRUE; #endif } - bool has_transactions() { return true; } + bool has_transactions() { return TRUE; } const char* index_type(uint key_number) { switch (get_index_type(key_number)) { @@ -147,8 +164,8 @@ class ha_ndbcluster: public handler void start_bulk_insert(ha_rows rows); int end_bulk_insert(); - static Ndb* seize_ndb(); - static void release_ndb(Ndb* ndb); + static Thd_ndb* seize_thd_ndb(); + static void release_thd_ndb(Thd_ndb* thd_ndb); uint8 table_cache_type() { return HA_CACHE_TBL_NOCACHE; } private: @@ -162,8 +179,6 @@ class ha_ndbcluster: public handler int build_index_list(TABLE *tab, enum ILBP phase); int get_metadata(const char* path); void release_metadata(); - const char* get_index_name(uint idx_no) const; - const char* get_unique_index_name(uint idx_no) const; NDB_INDEX_TYPE get_index_type(uint idx_no) const; NDB_INDEX_TYPE get_index_type_from_table(uint index_no) const; @@ -192,15 +207,14 @@ class ha_ndbcluster: public handler uint fieldnr, const byte* field_ptr); int set_ndb_key(NdbOperation*, Field *field, uint fieldnr, const byte* field_ptr); - int set_ndb_value(NdbOperation*, Field *field, uint fieldnr); - int get_ndb_value(NdbOperation*, Field *field, uint fieldnr); + int set_ndb_value(NdbOperation*, Field *field, uint fieldnr, bool *set_blob_value= 0); + int get_ndb_value(NdbOperation*, Field *field, uint fieldnr, byte*); friend int g_get_ndb_blobs_value(NdbBlob *ndb_blob, void *arg); int get_ndb_blobs_value(NdbBlob *last_ndb_blob); int set_primary_key(NdbOperation *op, const byte *key); int set_primary_key(NdbOperation *op); int set_primary_key_from_old_data(NdbOperation *op, const byte *old_data); - int set_bounds(NdbIndexScanOperation *ndb_op, const key_range *key, - int bound); + int set_bounds(NdbIndexScanOperation *ndb_op, const key_range *keys[2]); int key_cmp(uint keynr, const byte * old_row, const byte * new_row); void print_results(); @@ -208,13 +222,16 @@ class ha_ndbcluster: public handler int ndb_err(NdbConnection*); bool uses_blob_value(bool all_fields); + int write_ndb_file(); + private: int check_ndb_connection(); NdbConnection *m_active_trans; NdbResultSet *m_active_cursor; Ndb *m_ndb; - void *m_table; + void *m_table; + void *m_table_info; char m_dbname[FN_HEADLEN]; //char m_schemaname[FN_HEADLEN]; char m_tabname[FN_HEADLEN]; @@ -226,6 +243,7 @@ class ha_ndbcluster: public handler typedef union { NdbRecAttr *rec; NdbBlob *blob; void *ptr; } NdbValue; NdbValue m_value[NDB_MAX_ATTRIBUTES_IN_TABLE]; bool m_use_write; + bool m_ignore_dup_key_not_supported; bool retrieve_all_fields; ha_rows rows_to_insert; ha_rows rows_inserted; @@ -238,6 +256,15 @@ class ha_ndbcluster: public handler char *blobs_buffer; uint32 blobs_buffer_size; uint dupkey; + + void set_rec_per_key(); + void records_update(); + void no_uncommitted_rows_execute_failure(); + void no_uncommitted_rows_update(int); + void no_uncommitted_rows_init(THD *); + void no_uncommitted_rows_reset(THD *); + + friend int execute_no_commit(ha_ndbcluster*, NdbConnection*); }; bool ndbcluster_init(void); @@ -248,8 +275,11 @@ int ndbcluster_rollback(THD *thd, void* ndb_transaction); void ndbcluster_close_connection(THD *thd); -int ndbcluster_discover(const char* dbname, const char* name, +int ndbcluster_discover(THD* thd, const char* dbname, const char* name, const void** frmblob, uint* frmlen); +int ndbcluster_find_files(THD *thd,const char *db,const char *path, + const char *wild, bool dir, List<char> *files); +int ndbcluster_table_exists(THD* thd, const char *db, const char *name); int ndbcluster_drop_database(const char* path); void ndbcluster_print_error(int error, const NdbOperation *error_op); diff --git a/sql/handler.cc b/sql/handler.cc index 5185e7f8921..edb47865e8d 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -103,7 +103,7 @@ const char *tx_isolation_names[] = { "READ-UNCOMMITTED", "READ-COMMITTED", "REPEATABLE-READ", "SERIALIZABLE", NullS}; TYPELIB tx_isolation_typelib= {array_elements(tx_isolation_names)-1,"", - tx_isolation_names}; + tx_isolation_names, NULL}; enum db_type ha_resolve_by_name(const char *name, uint namelen) { @@ -555,7 +555,7 @@ int ha_commit_trans(THD *thd, THD_TRANS* trans) query_cache.invalidate(thd->transaction.changed_tables); #endif /*HAVE_QUERY_CACHE*/ if (error && trans == &thd->transaction.all && mysql_bin_log.is_open()) - sql_print_error("Error: Got error during commit; Binlog is not up to date!"); + sql_print_error("Got error during commit; Binlog is not up to date!"); thd->variables.tx_isolation=thd->session_tx_isolation; if (operation_done) { @@ -939,23 +939,6 @@ int handler::read_first_row(byte * buf, uint primary_key) } -/* Set a timestamp in record */ - -void handler::update_timestamp(byte *record) -{ - long skr= (long) table->in_use->query_start(); -#ifdef WORDS_BIGENDIAN - if (table->db_low_byte_first) - { - int4store(record,skr); - } - else -#endif - longstore(record,skr); - return; -} - - /* Generate the next auto-increment number based on increment and offset @@ -1240,6 +1223,21 @@ void handler::print_error(int error, myf errflag) case HA_ERR_NO_REFERENCED_ROW: textno=ER_NO_REFERENCED_ROW; break; + case HA_ERR_NO_SUCH_TABLE: + { + /* + We have to use path to find database name instead of using + table->table_cache_key because if the table didn't exist, then + table_cache_key was not set up + */ + char *db; + char buff[FN_REFLEN]; + uint length=dirname_part(buff,table->path); + buff[length-1]=0; + db=buff+dirname_length(buff); + my_error(ER_NO_SUCH_TABLE,MYF(0),db,table->table_name); + break; + } default: { /* The error was "unknown" to this function. @@ -1320,14 +1318,15 @@ int handler::rename_table(const char * from, const char * to) } /* - Tell the handler to turn on or off logging to the handler's recovery log + Tell the handler to turn on or off transaction in the handler */ -int ha_recovery_logging(THD *thd, bool on) +int ha_enable_transaction(THD *thd, bool on) { int error=0; - DBUG_ENTER("ha_recovery_logging"); + DBUG_ENTER("ha_enable_transaction"); + thd->transaction.on= on; DBUG_RETURN(error); } @@ -1385,6 +1384,71 @@ int ha_create_table(const char *name, HA_CREATE_INFO *create_info, DBUG_RETURN(error != 0); } +/* + Try to discover table from engine and + if found, write the frm file to disk. + + RETURN VALUES: + 0 : Table existed in engine and created + on disk if so requested + 1 : Table does not exist + >1 : error + +*/ + +int ha_create_table_from_engine(THD* thd, + const char *db, + const char *name, + bool create_if_found) +{ + int error; + const void *frmblob; + uint frmlen; + char path[FN_REFLEN]; + HA_CREATE_INFO create_info; + TABLE table; + DBUG_ENTER("ha_create_table_from_engine"); + DBUG_PRINT("enter", ("name '%s'.'%s' create_if_found: %d", + db, name, create_if_found)); + + bzero((char*) &create_info,sizeof(create_info)); + + if ((error= ha_discover(thd, db, name, &frmblob, &frmlen))) + DBUG_RETURN(error); + /* + Table exists in handler + frmblob and frmlen are set + */ + + if (create_if_found) + { + (void)strxnmov(path,FN_REFLEN,mysql_data_home,"/",db,"/",name,NullS); + // Save the frm file + if ((error = writefrm(path, frmblob, frmlen))) + goto err_end; + + if (openfrm(path,"",0,(uint) READ_ALL, 0, &table)) + DBUG_RETURN(1); + + update_create_info_from_table(&create_info, &table); + create_info.table_options|= HA_CREATE_FROM_ENGINE; + + if (lower_case_table_names == 2 && + !(table.file->table_flags() & HA_FILE_BASED)) + { + /* Ensure that handler gets name in lower case */ + my_casedn_str(files_charset_info, path); + } + + error=table.file->create(path,&table,&create_info); + VOID(closefrm(&table)); + } + +err_end: + my_free((char*) frmblob, MYF(MY_ALLOW_ZERO_PTR)); + DBUG_RETURN(error); +} + static int NEAR_F delete_file(const char *name,const char *ext,int extflag) { char buff[FN_REFLEN]; @@ -1490,17 +1554,21 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, /* Try to discover one table from handler(s) + + RETURN + 0 ok. In this case *frmblob and *frmlen are set + 1 error. frmblob and frmlen may not be set */ -int ha_discover(const char* dbname, const char* name, - const void** frmblob, uint* frmlen) +int ha_discover(THD *thd, const char *db, const char *name, + const void **frmblob, uint *frmlen) { int error= 1; // Table does not exist in any handler DBUG_ENTER("ha_discover"); - DBUG_PRINT("enter", ("db: %s, name: %s", dbname, name)); + DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); #ifdef HAVE_NDBCLUSTER_DB if (have_ndbcluster == SHOW_OPTION_YES) - error= ndbcluster_discover(dbname, name, frmblob, frmlen); + error= ndbcluster_discover(thd, db, name, frmblob, frmlen); #endif if (!error) statistic_increment(ha_discover_count,&LOCK_status); @@ -1509,6 +1577,55 @@ int ha_discover(const char* dbname, const char* name, /* + Call this function in order to give the handler the possiblity + to ask engine if there are any new tables that should be written to disk + or any dropped tables that need to be removed from disk +*/ + +int +ha_find_files(THD *thd,const char *db,const char *path, + const char *wild, bool dir, List<char> *files) +{ + int error= 0; + DBUG_ENTER("ha_find_files"); + DBUG_PRINT("enter", ("db: %s, path: %s, wild: %s, dir: %d", + db, path, wild, dir)); +#ifdef HAVE_NDBCLUSTER_DB + if (have_ndbcluster == SHOW_OPTION_YES) + error= ndbcluster_find_files(thd, db, path, wild, dir, files); +#endif + DBUG_RETURN(error); + + +} + +#ifdef NOT_YET_USED + +/* + Ask handler if the table exists in engine + + RETURN + 0 Table does not exist + 1 Table exists + # Error code + + */ +int ha_table_exists(THD* thd, const char* db, const char* name) +{ + int error= 2; + DBUG_ENTER("ha_table_exists"); + DBUG_PRINT("enter", ("db: %s, name: %s", db, name)); +#ifdef HAVE_NDBCLUSTER_DB + if (have_ndbcluster == SHOW_OPTION_YES) + error= ndbcluster_table_exists(thd, db, name); +#endif + DBUG_RETURN(error); +} + +#endif + + +/* Read first row between two ranges. Store ranges for future calls to read_range_next @@ -1554,9 +1671,9 @@ int handler::read_range_first(const key_range *start_key, start_key->length, start_key->flag); if (result) - DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND || - result == HA_ERR_END_OF_FILE) ? HA_ERR_END_OF_FILE : - result); + DBUG_RETURN((result == HA_ERR_KEY_NOT_FOUND) + ? HA_ERR_END_OF_FILE + : result); DBUG_RETURN (compare_key(end_range) <= 0 ? 0 : HA_ERR_END_OF_FILE); } diff --git a/sql/handler.h b/sql/handler.h index 9a08b8ed78c..1a7233c4e99 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -287,7 +287,6 @@ public: {} virtual ~handler(void) { /* TODO: DBUG_ASSERT(inited == NONE); */ } int ha_open(const char *name, int mode, int test_if_locked); - void update_timestamp(byte *record); void update_auto_increment(); virtual void print_error(int error, myf errflag); virtual bool get_error_message(int error, String *buf); @@ -300,7 +299,15 @@ public: virtual const key_map *keys_to_use_for_scanning() { return &key_map_empty; } virtual bool has_transactions(){ return 0;} virtual uint extra_rec_buf_length() { return 0; } - virtual ha_rows estimate_number_of_rows() { return records+EXTRA_RECORDS; } + + /* + Return upper bound of current number of records in the table + (max. of how many records one will retrieve when doing a full table scan) + If upper bound is not known, HA_POS_ERROR should be returned as a max + possible upper bound. + */ + virtual ha_rows estimate_rows_upper_bound() + { return records+EXTRA_RECORDS; } virtual const char *index_type(uint key_number) { DBUG_ASSERT(0); return "";} @@ -546,6 +553,8 @@ void ha_close_connection(THD* thd); enum db_type ha_checktype(enum db_type database_type); int ha_create_table(const char *name, HA_CREATE_INFO *create_info, bool update_create_info); +int ha_create_table_from_engine(THD* thd, const char *db, const char *name, + bool create_if_found); int ha_delete_table(enum db_type db_type, const char *path); void ha_drop_database(char* path); int ha_init_key_cache(const char *name, KEY_CACHE *key_cache); @@ -564,8 +573,13 @@ int ha_savepoint(THD *thd, char *savepoint_name); int ha_autocommit_or_rollback(THD *thd, int error); void ha_set_spin_retries(uint retries); bool ha_flush_logs(void); -int ha_recovery_logging(THD *thd, bool on); +int ha_enable_transaction(THD *thd, bool on); int ha_change_key_cache(KEY_CACHE *old_key_cache, KEY_CACHE *new_key_cache); -int ha_discover(const char* dbname, const char* name, +int ha_discover(THD* thd, const char* dbname, const char* name, const void** frmblob, uint* frmlen); +int ha_find_files(THD *thd,const char *db,const char *path, + const char *wild, bool dir,List<char>* files); +int ha_table_exists(THD* thd, const char* db, const char* name); + + diff --git a/sql/item.cc b/sql/item.cc index 134b04d5540..7476f5af312 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -113,13 +113,14 @@ void Item::cleanup() DBUG_PRINT("info", ("Item: 0x%lx", this)); DBUG_PRINT("info", ("Type: %d", (int)type())); fixed=0; + marker= 0; DBUG_VOID_RETURN; } Item_ident::Item_ident(const char *db_name_par,const char *table_name_par, const char *field_name_par) :orig_db_name(db_name_par), orig_table_name(table_name_par), - orig_field_name(field_name_par), changed_during_fix_field(0), + orig_field_name(field_name_par), db_name(db_name_par), table_name(table_name_par), field_name(field_name_par), cached_field_index(NO_CACHED_FIELD_INDEX), cached_table(0), depended_from(0) @@ -133,7 +134,6 @@ Item_ident::Item_ident(THD *thd, Item_ident *item) orig_db_name(item->orig_db_name), orig_table_name(item->orig_table_name), orig_field_name(item->orig_field_name), - changed_during_fix_field(0), db_name(item->db_name), table_name(item->table_name), field_name(item->field_name), @@ -150,11 +150,6 @@ void Item_ident::cleanup() table_name, orig_table_name, field_name, orig_field_name)); Item::cleanup(); - if (changed_during_fix_field) - { - *changed_during_fix_field= this; - changed_during_fix_field= 0; - } db_name= orig_db_name; table_name= orig_table_name; field_name= orig_field_name; @@ -451,19 +446,47 @@ Item_field::Item_field(Field *f) have_privileges(0), any_privileges(0) { set_field(f); - collation.set(DERIVATION_IMPLICIT); - fixed= 1; + /* + field_name and talbe_name should not point to garbage + if this item is to be reused + */ + orig_table_name= orig_field_name= ""; } Item_field::Item_field(THD *thd, Field *f) - :Item_ident(NullS, thd->strdup(f->table_name), - thd->strdup(f->field_name)), + :Item_ident(f->table->table_cache_key, f->table_name, f->field_name), item_equal(0), no_const_subst(0), have_privileges(0), any_privileges(0) { + /* + We always need to provide Item_field with a fully qualified field + name to avoid ambiguity when executing prepared statements like + SELECT * from d1.t1, d2.t1; (assuming d1.t1 and d2.t1 have columns + with same names). + This is because prepared statements never deal with wildcards in + select list ('*') and always fix fields using fully specified path + (i.e. db.table.column). + No check for OOM: if db_name is NULL, we'll just get + "Field not found" error. + We need to copy db_name, table_name and field_name because they must + be allocated in the statement memory, not in table memory (the table + structure can go away and pop up again between subsequent executions + of a prepared statement). + */ + if (thd->current_arena->is_stmt_prepare()) + { + if (db_name) + orig_db_name= thd->strdup(db_name); + orig_table_name= thd->strdup(table_name); + orig_field_name= thd->strdup(field_name); + /* + We don't restore 'name' in cleanup because it's not changed + during execution. Still we need it to point to persistent + memory if this item is to be reused. + */ + name= (char*) orig_field_name; + } set_field(f); - collation.set(DERIVATION_IMPLICIT); - fixed= 1; } // Constructor need to process subselect with temporary tables (see Item) @@ -490,6 +513,21 @@ void Item_field::set_field(Field *field_par) db_name=field_par->table->table_cache_key; unsigned_flag=test(field_par->flags & UNSIGNED_FLAG); collation.set(field_par->charset(), DERIVATION_IMPLICIT); + fixed= 1; +} + + +/* + Reset this item to point to a field from the new temporary table. + This is used when we create a new temporary table for each execution + of prepared statement. +*/ + +void Item_field::reset_field(Field *f) +{ + set_field(f); + /* 'name' is pointing at field->field_name of old field */ + name= (char*) f->field_name; } const char *Item_ident::full_name() const @@ -1042,7 +1080,7 @@ int Item_param::save_in_field(Field *field, bool no_conversions) return field->store(str_value.ptr(), str_value.length(), str_value.charset()); case NULL_VALUE: - return set_field_to_null(field); + return set_field_to_null_with_conversions(field, no_conversions); case NO_VALUE: default: DBUG_ASSERT(0); @@ -1145,9 +1183,10 @@ String *Item_param::val_str(String* str) return str; case TIME_VALUE: { - if (str->reserve(MAX_DATE_REP_LENGTH)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH)) break; - TIME_to_string(&value.time, str); + str->length((uint) my_TIME_to_str(&value.time, (char*) str->ptr())); + str->set_charset(&my_charset_bin); return str; } case NULL_VALUE: @@ -1177,24 +1216,19 @@ const String *Item_param::query_val_str(String* str) const case TIME_VALUE: { char *buf, *ptr; - String tmp; str->length(0); /* TODO: in case of error we need to notify replication that binary log contains wrong statement */ - if (str->reserve(MAX_DATE_REP_LENGTH+3)) + if (str->reserve(MAX_DATE_STRING_REP_LENGTH+3)) break; /* Create date string inplace */ buf= str->c_ptr_quick(); ptr= buf; *ptr++= '\''; - tmp.set(ptr, MAX_DATE_REP_LENGTH, &my_charset_bin); - tmp.length(0); - TIME_to_string(&value.time, &tmp); - - ptr+= tmp.length(); + ptr+= (uint) my_TIME_to_str(&value.time, ptr); *ptr++= '\''; str->length((uint32) (ptr - buf)); break; @@ -1372,7 +1406,7 @@ bool Item_ref_null_helper::get_date(TIME *ltime, uint fuzzydate) static void mark_as_dependent(THD *thd, SELECT_LEX *last, SELECT_LEX *current, Item_ident *item) { - // store pointer on SELECT_LEX from wich item is dependent + // store pointer on SELECT_LEX from which item is dependent item->depended_from= last; current->mark_as_dependent(last); if (thd->lex->describe & DESCRIBE_EXTENDED) @@ -1416,6 +1450,7 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) TABLE_LIST *table_list; Item **refer= (Item **)not_found_item; uint counter; + bool not_used; // Prevent using outer fields in subselects, that is not supported now SELECT_LEX *cursel= (SELECT_LEX *) thd->lex->current_select; if (cursel->master_unit()->first_select()->linkage != DERIVED_TABLE_TYPE) @@ -1440,11 +1475,9 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) enum_parsing_place place= prev_subselect_item->parsing_place; /* check table fields only if subquery used somewhere out of HAVING - or SELECT list or outer SELECT do not use groupping (i.e. tables - are accessable) + or outer SELECT do not use groupping (i.e. tables are accessable) */ - if (((place != IN_HAVING && - place != SELECT_LIST) || + if ((place != IN_HAVING || (sl->with_sum_func == 0 && sl->group_list.elements == 0)) && (tmp= find_field_in_tables(thd, this, table_list, ref, @@ -1469,7 +1502,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) } if (sl->resolve_mode == SELECT_LEX::SELECT_MODE && (refer= find_item_in_list(this, sl->item_list, &counter, - REPORT_EXCEPT_NOT_FOUND)) != + REPORT_EXCEPT_NOT_FOUND, + ¬_used)) != (Item **) not_found_item) { if (*refer && (*refer)->fixed) // Avoid crash in case of error @@ -1517,14 +1551,11 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) return -1; } - Item_ref *rf; - *ref= rf= new Item_ref(last->ref_pointer_array + counter, - ref, - (char *)table_name, - (char *)field_name); - register_item_tree_changing(ref); + Item_ref *rf= new Item_ref(last->ref_pointer_array + counter, + (char *)table_name, (char *)field_name); if (!rf) return 1; + thd->change_item_tree(ref, rf); /* rf is Item_ref => never substitute other items (in this case) during fix_fields() => we can use rf after fix_fields() @@ -1541,10 +1572,8 @@ bool Item_field::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) if (last->having_fix_field) { Item_ref *rf; - *ref= rf= new Item_ref(ref, *ref, - (cached_table->db[0]?cached_table->db:0), - (char *)cached_table->alias, - (char *)field_name); + rf= new Item_ref((cached_table->db[0] ? cached_table->db : 0), + (char*) cached_table->alias, (char*) field_name); if (!rf) return 1; /* @@ -1754,7 +1783,9 @@ void Item::init_make_field(Send_field *tmp_field, tmp_field->table_name= empty_name; tmp_field->col_name= name; tmp_field->charsetnr= collation.collation->number; - tmp_field->flags=maybe_null ? 0 : NOT_NULL_FLAG; + tmp_field->flags= (maybe_null ? 0 : NOT_NULL_FLAG) | + (my_binary_compare(collation.collation) ? + BINARY_FLAG : 0); tmp_field->type=field_type; tmp_field->length=max_length; tmp_field->decimals=decimals; @@ -2261,15 +2292,17 @@ bool Item_field::send(Protocol *protocol, String *buffer) return protocol->store(result_field); } + /* This is used for HAVING clause Find field in select list having the same name - */ +*/ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) { DBUG_ASSERT(fixed == 0); uint counter; + bool not_used; if (!ref) { TABLE_LIST *table_list; @@ -2289,13 +2322,14 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) first_select()->linkage != DERIVED_TABLE_TYPE) ? REPORT_EXCEPT_NOT_FOUND : - REPORT_ALL_ERRORS))) == + REPORT_ALL_ERRORS), ¬_used)) == (Item **)not_found_item) { - upward_lookup= 1; Field *tmp= (Field*) not_found_field; + SELECT_LEX *last= 0; + upward_lookup= 1; /* - We can't find table field in table list of current select, + We can't find table field in select list of current select, consequently we have to find it in outer subselect(s). We can't join lists of outer & current select, because of scope of view rules. For example if both tables (outer & current) have @@ -2303,15 +2337,14 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) mention of table name, but if we join tables in one list it will cause error ER_NON_UNIQ_ERROR in find_item_in_list. */ - SELECT_LEX *last=0; for ( ; sl ; sl= (prev_unit= sl->master_unit())->outer_select()) { last= sl; Item_subselect *prev_subselect_item= prev_unit->item; if (sl->resolve_mode == SELECT_LEX::SELECT_MODE && (ref= find_item_in_list(this, sl->item_list, - &counter, - REPORT_EXCEPT_NOT_FOUND)) != + &counter, REPORT_EXCEPT_NOT_FOUND, + ¬_used)) != (Item **)not_found_item) { if (*ref && (*ref)->fixed) // Avoid crash in case of error @@ -2333,8 +2366,7 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) or SELECT list or outer SELECT do not use groupping (i.e. tables are accessable) */ - if (((place != IN_HAVING && - place != SELECT_LIST) || + if ((place != IN_HAVING || (sl->with_sum_func == 0 && sl->group_list.elements == 0)) && (tmp= find_field_in_tables(thd, this, table_list, reference, @@ -2369,9 +2401,9 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) if (!ref) return 1; - else if (!tmp) + if (!tmp) return -1; - else if (ref == (Item **)not_found_item && tmp == not_found_field) + if (ref == (Item **)not_found_item && tmp == not_found_field) { if (upward_lookup) { @@ -2384,22 +2416,26 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) // Call to report error find_item_in_list(this, *(thd->lex->current_select->get_item_list()), - &counter, - REPORT_ALL_ERRORS); + &counter, REPORT_ALL_ERRORS, ¬_used); } - ref= 0; + ref= 0; // Safety return 1; } - else if (tmp != not_found_field) + if (tmp != not_found_field) { - ref= 0; // To prevent "delete *ref;" on ~Item_erf() of this item + /* + Set ref to 0 as we are replacing this item with the found item + and this will ensure we get an error if this item would be + used elsewhere + */ + ref= 0; // Safety if (tmp != view_ref_found) { Item_field* fld; - if (!((*reference)= fld= new Item_field(tmp))) + if (!(fld= new Item_field(tmp))) return 1; + thd->change_item_tree(reference, fld); mark_as_dependent(thd, last, thd->lex->current_select, fld); - register_item_tree_changing(reference); return 0; } /* @@ -2416,15 +2452,15 @@ bool Item_ref::fix_fields(THD *thd, TABLE_LIST *tables, Item **reference) } else { - if (!(*ref)->fixed) - { - my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, - "forward reference in item list"); - return -1; - } - mark_as_dependent(thd, last, thd->lex->current_select, - this); - ref= last->ref_pointer_array + counter; + if (!(*ref)->fixed) + { + my_error(ER_ILLEGAL_REFERENCE, MYF(0), name, + "forward reference in item list"); + return -1; + } + mark_as_dependent(thd, last, thd->lex->current_select, + this); + ref= last->ref_pointer_array + counter; } } else if (!ref) @@ -2478,8 +2514,6 @@ void Item_ref::cleanup() DBUG_ENTER("Item_ref::cleanup"); Item_ident::cleanup(); result_field= 0; - if (hook_ptr) - *hook_ptr= orig_item; DBUG_VOID_RETURN; } @@ -2602,7 +2636,6 @@ bool Item_default_value::fix_fields(THD *thd, def_field->move_field(def_field->table->default_values - def_field->table->record[0]); set_field(def_field); - fixed= 1; return 0; } @@ -2660,7 +2693,6 @@ bool Item_insert_value::fix_fields(THD *thd, set_field(new Field_null(0, 0, Field::NONE, tmp_field->field_name, tmp_field->table, &my_charset_bin)); } - fixed= 1; return 0; } @@ -2779,10 +2811,12 @@ Item_result item_cmp_type(Item_result a,Item_result b) } -Item *resolve_const_item(Item *item,Item *comp_item) +void resolve_const_item(THD *thd, Item **ref, Item *comp_item) { + Item *item= *ref; + Item *new_item; if (item->basic_const_item()) - return item; // Can't be better + return; // Can't be better Item_result res_type=item_cmp_type(comp_item->result_type(), item->result_type()); char *name=item->name; // Alloced by sql_alloc @@ -2793,27 +2827,32 @@ Item *resolve_const_item(Item *item,Item *comp_item) String tmp(buff,sizeof(buff),&my_charset_bin),*result; result=item->val_str(&tmp); if (item->null_value) - return new Item_null(name); - uint length=result->length(); - char *tmp_str=sql_strmake(result->ptr(),length); - return new Item_string(name,tmp_str,length,result->charset()); + new_item= new Item_null(name); + else + { + uint length= result->length(); + char *tmp_str= sql_strmake(result->ptr(), length); + new_item= new Item_string(name, tmp_str, length, result->charset()); + } } - if (res_type == INT_RESULT) + else if (res_type == INT_RESULT) { longlong result=item->val_int(); uint length=item->max_length; bool null_value=item->null_value; - return (null_value ? (Item*) new Item_null(name) : - (Item*) new Item_int(name,result,length)); + new_item= (null_value ? (Item*) new Item_null(name) : + (Item*) new Item_int(name, result, length)); } else { // It must REAL_RESULT double result=item->val(); uint length=item->max_length,decimals=item->decimals; bool null_value=item->null_value; - return (null_value ? (Item*) new Item_null(name) : - (Item*) new Item_real(name,result,decimals,length)); + new_item= (null_value ? (Item*) new Item_null(name) : (Item*) + new Item_real(name, result, decimals, length)); } + if (new_item) + thd->change_item_tree(ref, new_item); } /* @@ -3040,6 +3079,7 @@ Item_type_holder::Item_type_holder(THD *thd, Item *item) else field_example= 0; max_length= real_length(item); + maybe_null= item->maybe_null; collation.set(item->collation); } @@ -3057,62 +3097,90 @@ static Item_result type_convertor[4][4]= {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT}, {ROW_RESULT, ROW_RESULT, ROW_RESULT, ROW_RESULT}}; + +/* + Values of 'from' field can be stored in 'to' field. + + SYNOPSIS + is_attr_compatible() + from Item which values should be saved + to Item where values should be saved + + RETURN + 1 can be saved + 0 can not be saved +*/ + +inline bool is_attr_compatible(Item *from, Item *to) +{ + return ((to->max_length >= from->max_length) && + (to->maybe_null || !from->maybe_null) && + (to->result_type() != STRING_RESULT || + from->result_type() != STRING_RESULT || + my_charset_same(from->collation.collation, + to->collation.collation))); +} + + bool Item_type_holder::join_types(THD *thd, Item *item) { uint32 new_length= real_length(item); - bool change_field= 0, skip_store_field= 0; - Item_result new_type= type_convertor[item_type][item->result_type()]; + bool use_new_field= 0, use_expression_type= 0; + Item_result new_result_type= type_convertor[item_type][item->result_type()]; + bool item_is_a_field= item->type() == Item::FIELD_ITEM; - // we have both fields - if (field_example && item->type() == Item::FIELD_ITEM) + /* + Check if both items point to fields: in this case we + can adjust column types of result table in the union smartly. + */ + if (field_example && item_is_a_field) { Field *field= ((Item_field *)item)->field; - if (field_example->field_cast_type() != field->field_cast_type()) + /* Can 'field_example' field store data of the column? */ + if ((use_new_field= + (!field->field_cast_compatible(field_example->field_cast_type()) || + !is_attr_compatible(item, this)))) { - if (!(change_field= - field_example->field_cast_compatible(field->field_cast_type()))) - { - /* - if old field can't store value of 'worse' new field we will make - decision about result field type based only on Item result type - */ - if (!field->field_cast_compatible(field_example->field_cast_type())) - skip_store_field= 1; - } + /* + The old field can't store value of the new field. + Check if the new field can store value of the old one. + */ + use_expression_type|= + (!field_example->field_cast_compatible(field->field_cast_type()) || + !is_attr_compatible(this, item)); } } - - // size/type should be changed - if (change_field || - (new_type != item_type) || - (max_length < new_length) || - ((new_type == INT_RESULT) && - (decimals < item->decimals)) || - (!maybe_null && item->maybe_null) || - (item_type == STRING_RESULT && new_type == STRING_RESULT && - !my_charset_same(collation.collation, item->collation.collation))) - { - // new field has some parameters worse then current - skip_store_field|= (change_field && - (max_length > new_length) || - ((new_type == INT_RESULT) && - (decimals > item->decimals)) || - (maybe_null && !item->maybe_null) || - (item_type == STRING_RESULT && - new_type == STRING_RESULT && - !my_charset_same(collation.collation, - item->collation.collation))); + else if (field_example || item_is_a_field) + { /* - It is safe assign pointer on field, because it will be used just after - all JOIN::prepare calls and before any SELECT execution + Expression types can't be mixed with field types, we have to use + expression types. */ - if (skip_store_field || item->type() != Item::FIELD_ITEM) + use_new_field= 1; // make next if test easier + use_expression_type= 1; + } + + /* Check whether size/type of the result item should be changed */ + if (use_new_field || + (new_result_type != item_type) || (new_length > max_length) || + (!maybe_null && item->maybe_null) || + (item_type == STRING_RESULT && + collation.collation != item->collation.collation)) + { + const char *old_cs,*old_derivation; + if (use_expression_type || !item_is_a_field) field_example= 0; else + { + /* + It is safe to assign a pointer to field here, because it will be used + before any table is closed. + */ field_example= ((Item_field*) item)->field; + } - const char *old_cs= collation.collation->name, - *old_derivation= collation.derivation_name(); + old_cs= collation.collation->name; + old_derivation= collation.derivation_name(); if (item_type == STRING_RESULT && collation.aggregate(item->collation)) { my_error(ER_CANT_AGGREGATE_2COLLATIONS, MYF(0), @@ -3126,18 +3194,18 @@ bool Item_type_holder::join_types(THD *thd, Item *item) max_length= max(max_length, new_length); decimals= max(decimals, item->decimals); maybe_null|= item->maybe_null; - item_type= new_type; + item_type= new_result_type; } DBUG_ASSERT(item_type != ROW_RESULT); return 0; } + uint32 Item_type_holder::real_length(Item *item) { if (item->type() == Item::FIELD_ITEM) - { return ((Item_field *)item)->max_disp_length(); - } + switch (item->result_type()) { case STRING_RESULT: @@ -3173,6 +3241,14 @@ String *Item_type_holder::val_str(String*) return 0; } +void Item_result_field::cleanup() +{ + DBUG_ENTER("Item_result_field::cleanup()"); + Item::cleanup(); + result_field= 0; + DBUG_VOID_RETURN; +} + /***************************************************************************** ** Instantiate templates *****************************************************************************/ diff --git a/sql/item.h b/sql/item.h index f8d04b6c851..b39a95d796c 100644 --- a/sql/item.h +++ b/sql/item.h @@ -99,7 +99,8 @@ public: static void *operator new(size_t size) {return (void*) sql_alloc((uint) size); } static void *operator new(size_t size, MEM_ROOT *mem_root) { return (void*) alloc_root(mem_root, (uint) size); } - static void operator delete(void *ptr,size_t size) {} /*lint -e715 */ + static void operator delete(void *ptr,size_t size) {} + static void operator delete(void *ptr,size_t size, MEM_ROOT *mem_root) {} enum Type {FIELD_ITEM, FUNC_ITEM, SUM_FUNC_ITEM, STRING_ITEM, INT_ITEM, REAL_ITEM, NULL_ITEM, VARBIN_ITEM, @@ -219,7 +220,8 @@ public: a constant expression */ virtual bool basic_const_item() const { return 0; } - virtual Item *new_item() { return 0; } /* Only for const items */ + /* cloning of constant items (0 if it is not const) */ + virtual Item *new_item() { return 0; } virtual cond_result eq_cmp_result() const { return COND_OK; } inline uint float_length(uint decimals_par) const { return decimals != NOT_FIXED_DEC ? (DBL_DIG+2+decimals_par) : DBL_DIG+8;} @@ -237,17 +239,29 @@ public: virtual void print(String *str_arg) { str_arg->append(full_name()); } void print_item_w_name(String *); virtual void update_used_tables() {} - virtual void split_sum_func(Item **ref_pointer_array, List<Item> &fields) {} + virtual void split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields) {} virtual bool get_date(TIME *ltime,uint fuzzydate); virtual bool get_time(TIME *ltime); virtual bool get_date_result(TIME *ltime,uint fuzzydate) { return get_date(ltime,fuzzydate); } virtual bool is_null() { return 0; } + /* + it is "top level" item of WHERE clause and we do not need correct NULL + handling + */ virtual void top_level_item() {} + /* + set field of temporary table for Item which can be switched on temporary + table during query processing (groupping and so on) + */ virtual void set_result_field(Field *field) {} virtual bool is_result_field() { return 0; } virtual bool is_bool_func() { return 0; } virtual void save_in_result_field(bool no_conversions) {} + /* + set value of aggegate function in case of no rows for groupping were found + */ virtual void no_rows_in_result() {} virtual Item *copy_or_same(THD *thd) { return this; } virtual Item *copy_andor_structure(THD *thd) { return this; } @@ -407,6 +421,7 @@ public: class st_select_lex; class Item_ident :public Item { +protected: /* We have to store initial values of db_name, table_name and field_name to be able to restore them during cleanup() because they can be @@ -416,7 +431,6 @@ class Item_ident :public Item const char *orig_db_name; const char *orig_table_name; const char *orig_field_name; - Item **changed_during_fix_field; public: const char *db_name; const char *table_name; @@ -439,8 +453,6 @@ public: Item_ident(THD *thd, Item_ident *item); const char *full_name() const; void cleanup(); - void register_item_tree_changing(Item **ref) - { changed_during_fix_field= ref; } bool remove_dependence_processor(byte * arg); void print(String *str); @@ -474,13 +486,21 @@ public: field(0), result_field(0), item_equal(0), no_const_subst(0), have_privileges(0), any_privileges(0) { collation.set(DERIVATION_IMPLICIT); } - // Constructor need to process subselect with temporary tables (see Item) + /* + Constructor needed to process subselect with temporary tables (see Item) + */ Item_field(THD *thd, Item_field *item); /* - Constructor used inside setup_wild(), ensures that field and table - names will live as long as Item_field (important in prep. stmt.) + Constructor used inside setup_wild(), ensures that field, table, + and database names will live as long as Item_field (this is important + in prepared statements). */ Item_field(THD *thd, Field *field); + /* + If this constructor is used, fix_fields() won't work, because + db_name, table_name and column_name are unknown. It's necessary to call + reset_field() before fix_fields() for all fields created this way. + */ Item_field(Field *field); enum Type type() const { return FIELD_ITEM; } bool eq(const Item *item, bool binary_cmp) const; @@ -491,6 +511,7 @@ public: longlong val_int_result(); String *str_result(String* tmp); bool send(Protocol *protocol, String *str_arg); + void reset_field(Field *f); bool fix_fields(THD *, struct st_table_list *, Item **); void make_field(Send_field *tmp_field); int save_in_field(Field *field,bool no_conversions); @@ -873,8 +894,8 @@ public: class Item_empty_string :public Item_string { public: - Item_empty_string(const char *header,uint length) :Item_string("",0, - &my_charset_bin) + Item_empty_string(const char *header,uint length, CHARSET_INFO *cs= NULL) : + Item_string("",0, cs ? cs : &my_charset_bin) { name=(char*) header; max_length=length;} void make_field(Send_field *field); }; @@ -931,6 +952,7 @@ public: { save_in_field(result_field, no_conversions); } + void cleanup(); }; @@ -939,20 +961,13 @@ class Item_ref :public Item_ident public: Field *result_field; /* Save result here */ Item **ref; - Item **hook_ptr; /* These two to restore */ - Item *orig_item; /* things in 'cleanup()' */ - Item_ref(Item **hook, Item *original,const char *db_par, - const char *table_name_par, const char *field_name_par) - :Item_ident(db_par, table_name_par, field_name_par), result_field(0), - ref(0), hook_ptr(hook), orig_item(original) {} - Item_ref(Item **item, Item **hook, - const char *table_name_par, const char *field_name_par) - :Item_ident(NullS, table_name_par, field_name_par), result_field(0), - ref(item), hook_ptr(hook), orig_item(hook ? *hook:0) {} - // Constructor need to process subselect with temporary tables (see Item) - Item_ref(THD *thd, Item_ref *item, Item **hook) - :Item_ident(thd, item), result_field(item->result_field), ref(item->ref), - hook_ptr(hook), orig_item(hook ? *hook : 0) {} + Item_ref(const char *db_par, const char *table_name_par, + const char *field_name_par) + :Item_ident(db_par, table_name_par, field_name_par), result_field(0), ref(0) {} + Item_ref(Item **item, const char *table_name_par, const char *field_name_par) + :Item_ident(NullS, table_name_par, field_name_par), result_field(0), ref(item) {} + /* Constructor need to process subselect with temporary tables (see Item) */ + Item_ref(THD *thd, Item_ref *item) :Item_ident(thd, item), result_field(item->result_field), ref(item->ref) {} enum Type type() const { return REF_ITEM; } bool eq(const Item *item, bool binary_cmp) const { return ref && (*ref)->eq(item, binary_cmp); } @@ -1009,7 +1024,6 @@ public: bool walk(Item_processor processor, byte *arg) { return (*ref)->walk(processor, arg); } void print(String *str); - void cleanup(); }; class Item_in_subselect; @@ -1020,7 +1034,7 @@ protected: public: Item_ref_null_helper(Item_in_subselect* master, Item **item, const char *table_name_par, const char *field_name_par): - Item_ref(item, NULL, table_name_par, field_name_par), owner(master) {} + Item_ref(item, table_name_par, field_name_par), owner(master) {} double val(); longlong val_int(); String* val_str(String* s); @@ -1458,5 +1472,5 @@ public: extern Item_buff *new_Item_buff(Item *item); extern Item_result item_cmp_type(Item_result a,Item_result b); -extern Item *resolve_const_item(Item *item,Item *cmp_item); +extern void resolve_const_item(THD *thd, Item **ref, Item *cmp_item); extern bool field_is_equal_to_item(Field *field,Item *item); diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 66af96f671f..ab8acdd465f 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -145,7 +145,7 @@ void Item_func_not_all::print(String *str) 1 Item was replaced with an integer version of the item */ -static bool convert_constant_item(Field *field, Item **item) +static bool convert_constant_item(THD *thd, Field *field, Item **item) { if ((*item)->const_item()) { @@ -153,7 +153,7 @@ static bool convert_constant_item(Field *field, Item **item) { Item *tmp=new Item_int_with_ref(field->val_int(), *item); if (tmp) - *item=tmp; + thd->change_item_tree(item, tmp); return 1; // Item was replaced } } @@ -164,6 +164,7 @@ static bool convert_constant_item(Field *field, Item **item) void Item_bool_func2::fix_length_and_dec() { max_length= 1; // Function returns 0 or 1 + THD *thd= current_thd; /* As some compare functions are generated after sql_yacc, @@ -188,17 +189,26 @@ void Item_bool_func2::fix_length_and_dec() { uint strong= 0; uint weak= 0; + uint32 dummy_offset; DTCollation coll; if (args[0]->result_type() == STRING_RESULT && args[1]->result_type() == STRING_RESULT && - !my_charset_same(args[0]->collation.collation, - args[1]->collation.collation) && + String::needs_conversion(0, args[0]->collation.collation, + args[1]->collation.collation, + &dummy_offset) && !coll.set(args[0]->collation, args[1]->collation, TRUE)) { Item* conv= 0; + Item_arena *arena= thd->current_arena, backup; strong= coll.strong; weak= strong ? 0 : 1; + /* + In case we're in statement prepare, create conversion item + in its memory: it will be reused on each execute. + */ + if (arena->is_stmt_prepare()) + thd->set_n_backup_item_arena(arena, &backup); if (args[weak]->type() == STRING_ITEM) { String tmp, cstr; @@ -211,26 +221,13 @@ void Item_bool_func2::fix_length_and_dec() } else { - THD *thd= current_thd; - /* - In case we're in prepared statement, create conversion - item in its memory: it will be reused on each execute. - (and don't juggle with mem_root's if it is ordinary statement). - We come here only during first fix_fields() because after creating - conversion item we will have arguments with compatible collations. - */ - Item_arena *arena= thd->current_arena, backup; - if (arena->is_conventional()) - arena= 0; - else - thd->set_n_backup_item_arena(arena, &backup); conv= new Item_func_conv_charset(args[weak], args[strong]->collation.collation); - if (arena) - thd->restore_backup_item_arena(arena, &backup); conv->collation.set(args[weak]->collation.derivation); conv->fix_fields(thd, 0, &conv); } + if (arena->is_stmt_prepare()) + thd->restore_backup_item_arena(arena, &backup); if (args[weak]->type() == FIELD_ITEM) ((Item_field *)args[weak])->no_const_subst= 1; args[weak]= conv ? conv : args[weak]; @@ -248,9 +245,9 @@ void Item_bool_func2::fix_length_and_dec() if (args[0]->type() == FIELD_ITEM) { Field *field=((Item_field*) args[0])->field; - if (field->store_for_compare()) + if (field->can_be_compared_as_longlong()) { - if (convert_constant_item(field,&args[1])) + if (convert_constant_item(thd, field,&args[1])) { cmp.set_cmp_func(this, tmp_arg, tmp_arg+1, INT_RESULT); // Works for all types. @@ -261,9 +258,9 @@ void Item_bool_func2::fix_length_and_dec() if (args[1]->type() == FIELD_ITEM /* && !args[1]->const_item() */) { Field *field=((Item_field*) args[1])->field; - if (field->store_for_compare()) + if (field->can_be_compared_as_longlong()) { - if (convert_constant_item(field,&args[0])) + if (convert_constant_item(thd, field,&args[0])) { cmp.set_cmp_func(this, tmp_arg, tmp_arg+1, INT_RESULT); // Works for all types. @@ -841,6 +838,7 @@ longlong Item_func_interval::val_int() void Item_func_between::fix_length_and_dec() { max_length= 1; + THD *thd= current_thd; /* As some compare functions are generated after sql_yacc, @@ -861,11 +859,15 @@ void Item_func_between::fix_length_and_dec() if (args[0]->type() == FIELD_ITEM) { Field *field=((Item_field*) args[0])->field; - if (field->store_for_compare()) + if (field->can_be_compared_as_longlong()) { - if (convert_constant_item(field,&args[1])) + /* + The following can't be recoded with || as convert_constant_item + changes the argument + */ + if (convert_constant_item(thd, field,&args[1])) cmp_type=INT_RESULT; // Works for all types. - if (convert_constant_item(field,&args[2])) + if (convert_constant_item(thd, field,&args[2])) cmp_type=INT_RESULT; // Works for all types. } } @@ -1167,6 +1169,15 @@ Item_func_nullif::val_str(String *str) return res; } + +bool +Item_func_nullif::is_null() +{ + if (!cmp.compare()) + return (null_value=1); + return 0; +} + /* CASE expression Return the matching ITEM or NULL if all compares (including else) failed @@ -1751,6 +1762,7 @@ void Item_func_in::fix_length_and_dec() { Item **arg, **arg_end; uint const_itm= 1; + THD *thd= current_thd; agg_cmp_type(&cmp_type, args, arg_count); @@ -1788,6 +1800,9 @@ void Item_func_in::fix_length_and_dec() Conversion is possible: All IN arguments are constants. */ + Item_arena *arena= thd->current_arena, backup; + if (arena->is_stmt_prepare()) + thd->set_n_backup_item_arena(arena, &backup); for (arg= args+1, arg_end= args+arg_count; arg < arg_end; arg++) { if (!my_charset_same(cmp_collation.collation, @@ -1803,6 +1818,8 @@ void Item_func_in::fix_length_and_dec() arg[0]= conv; } } + if (arena->is_stmt_prepare()) + thd->restore_backup_item_arena(arena, &backup); } } @@ -1830,7 +1847,7 @@ void Item_func_in::fix_length_and_dec() DBUG_ASSERT(0); return; } - if (array && !(current_thd->is_fatal_error)) // If not EOM + if (array && !(thd->is_fatal_error)) // If not EOM { uint j=0; for (uint i=1 ; i < arg_count ; i++) @@ -2048,7 +2065,8 @@ Item *Item_cond::transform(Item_transformer transformer, byte *arg) } -void Item_cond::split_sum_func(Item **ref_pointer_array, List<Item> &fields) +void Item_cond::split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields) { List_iterator<Item> li(list); Item *item; @@ -2057,13 +2075,15 @@ void Item_cond::split_sum_func(Item **ref_pointer_array, List<Item> &fields) while ((item=li++)) { if (item->with_sum_func && item->type() != SUM_FUNC_ITEM) - item->split_sum_func(ref_pointer_array, fields); + item->split_sum_func(thd, ref_pointer_array, fields); else if (item->used_tables() || item->type() == SUM_FUNC_ITEM) { + Item **ref= li.ref(); uint el= fields.elements; + Item *new_item= new Item_ref(ref_pointer_array + el, 0, item->name); fields.push_front(item); ref_pointer_array[el]= item; - li.replace(new Item_ref(ref_pointer_array + el, li.ref(), 0, item->name)); + thd->change_item_tree(ref, new_item); } item->update_used_tables(); used_tables_cache|=item->used_tables(); diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 8d52459c0e5..754ebe08b77 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -215,21 +215,11 @@ public: class Item_bool_rowready_func2 :public Item_bool_func2 { - Item *orig_a, *orig_b; /* propagate_const can change parameters */ public: - Item_bool_rowready_func2(Item *a,Item *b) :Item_bool_func2(a,b), - orig_a(a), orig_b(b) + Item_bool_rowready_func2(Item *a, Item *b) :Item_bool_func2(a, b) { allowed_arg_cols= a->cols(); } - void cleanup() - { - DBUG_ENTER("Item_bool_rowready_func2::cleanup"); - Item_bool_func2::cleanup(); - tmp_arg[0]= orig_a; - tmp_arg[1]= orig_b; - DBUG_VOID_RETURN; - } Item *neg_transformer(THD *thd); virtual Item *negated_item(); }; @@ -483,6 +473,7 @@ public: const char *func_name() const { return "nullif"; } void print(String *str) { Item_func::print(str); } table_map not_null_tables() const { return 0; } + bool is_null(); }; @@ -893,7 +884,7 @@ public: char escape; Item_func_like(Item *a,Item *b, Item *escape_arg) - :Item_bool_func2(a,b), canDoTurboBM(false), pattern(0), pattern_len(0), + :Item_bool_func2(a,b), canDoTurboBM(FALSE), pattern(0), pattern_len(0), bmGs(0), bmBc(0), escape_item(escape_arg) {} longlong val_int(); enum Functype functype() const { return LIKE_FUNC; } @@ -970,7 +961,7 @@ public: table_map used_tables() const; void update_used_tables(); void print(String *str); - void split_sum_func(Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); friend int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds); void top_level_item() { abort_on_null=1; } void copy_andor_arguments(THD *thd, Item_cond *item); diff --git a/sql/item_func.cc b/sql/item_func.cc index fe5112cd75b..58ef44cc225 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -299,20 +299,22 @@ Item *Item_func::transform(Item_transformer transformer, byte *argument) } -void Item_func::split_sum_func(Item **ref_pointer_array, List<Item> &fields) +void Item_func::split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields) { Item **arg, **arg_end; for (arg= args, arg_end= args+arg_count; arg != arg_end ; arg++) { Item *item=* arg; if (item->with_sum_func && item->type() != SUM_FUNC_ITEM) - item->split_sum_func(ref_pointer_array, fields); + item->split_sum_func(thd, ref_pointer_array, fields); else if (item->used_tables() || item->type() == SUM_FUNC_ITEM) { uint el= fields.elements; + Item *new_item= new Item_ref(ref_pointer_array + el, 0, item->name); fields.push_front(item); ref_pointer_array[el]= item; - *arg= new Item_ref(ref_pointer_array + el, arg, 0, item->name); + thd->change_item_tree(arg, new_item); } } } @@ -718,8 +720,8 @@ void Item_func_int_div::fix_length_and_dec() double Item_func_mod::val() { DBUG_ASSERT(fixed == 1); - double value= floor(args[0]->val()+0.5); - double val2=floor(args[1]->val()+0.5); + double value= args[0]->val(); + double val2= args[1]->val(); if ((null_value= args[0]->null_value || args[1]->null_value)) return 0.0; /* purecov: inspected */ if (val2 == 0.0) @@ -747,10 +749,7 @@ longlong Item_func_mod::val_int() void Item_func_mod::fix_length_and_dec() { - max_length=args[1]->max_length; - decimals=0; - maybe_null=1; - find_num_type(); + Item_num_op::fix_length_and_dec(); } @@ -1087,21 +1086,38 @@ double Item_func_round::val() } -void Item_func_rand::fix_length_and_dec() +bool Item_func_rand::fix_fields(THD *thd, struct st_table_list *tables, + Item **ref) { - decimals=NOT_FIXED_DEC; - max_length=float_length(decimals); + Item_real_func::fix_fields(thd, tables, ref); used_tables_cache|= RAND_TABLE_BIT; if (arg_count) { // Only use argument once in query - uint32 tmp= (uint32) (args[0]->val_int()); - if ((rand= (struct rand_struct*) sql_alloc(sizeof(*rand)))) - randominit(rand,(uint32) (tmp*0x10001L+55555555L), - (uint32) (tmp*0x10000001L)); + /* + Allocate rand structure once: we must use thd->current_arena + to create rand in proper mem_root if it's a prepared statement or + stored procedure. + */ + if (!rand && !(rand= (struct rand_struct*) + thd->current_arena->alloc(sizeof(*rand)))) + return TRUE; + /* + PARAM_ITEM is returned if we're in statement prepare and consequently + no placeholder value is set yet. + */ + if (args[0]->type() != PARAM_ITEM) + { + /* + TODO: do not do reinit 'rand' for every execute of PS/SP if + args[0] is a constant. + */ + uint32 tmp= (uint32) args[0]->val_int(); + randominit(rand, (uint32) (tmp*0x10001L+55555555L), + (uint32) (tmp*0x10000001L)); + } } else { - THD *thd= current_thd; /* No need to send a Rand log event if seed was given eg: RAND(seed), as it will be replicated in the query as such. @@ -1115,6 +1131,7 @@ void Item_func_rand::fix_length_and_dec() thd->rand_saved_seed2=thd->rand.seed2; rand= &thd->rand; } + return FALSE; } void Item_func_rand::update_used_tables() @@ -1534,10 +1551,11 @@ longlong Item_func_find_in_set::val_int() { const char *substr_end= str_end + symbol_len; bool is_last_item= (substr_end == real_end); - if (wc == (my_wc_t) separator || is_last_item) + bool is_separator= (wc == (my_wc_t) separator); + if (is_separator || is_last_item) { position++; - if (is_last_item) + if (is_last_item && !is_separator) str_end= substr_end; if (!my_strnncoll(cs, (const uchar *) str_begin, str_end - str_begin, @@ -2820,7 +2838,21 @@ void Item_func_get_user_var::fix_length_and_dec() error= get_var_with_binlog(thd, name, &var_entry); if (var_entry) + { collation.set(var_entry->collation); + switch (var_entry->type) { + case REAL_RESULT: + max_length= DBL_DIG + 8; + case INT_RESULT: + max_length= MAX_BIGINT_WIDTH; + break; + case STRING_RESULT: + max_length= MAX_BLOB_WIDTH; + break; + case ROW_RESULT: // Keep compiler happy + break; + } + } else null_value= 1; @@ -2937,10 +2969,10 @@ void Item_func_match::init_search(bool no_order) if (key == NO_SUCH_KEY) { List<Item> fields; + fields.push_back(new Item_string(" ",1, cmp_collation.collation)); for (uint i=1; i < arg_count; i++) fields.push_back(args[i]); - concat=new Item_func_concat_ws(new Item_string(" ",1, - cmp_collation.collation), fields); + concat=new Item_func_concat_ws(fields); /* Above function used only to get value and do not need fix_fields for it: Item_string - basic constant diff --git a/sql/item_func.h b/sql/item_func.h index a5448f54693..3b941c4a40c 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -125,7 +125,7 @@ public: void set_arguments(List<Item> &list); inline uint argument_count() const { return arg_count; } inline void remove_arguments() { arg_count=0; } - virtual void split_sum_func(Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); void print(String *str); void print_op(String *str); void print_args(String *str, uint from); @@ -220,6 +220,7 @@ class Item_func_signed :public Item_int_func { public: Item_func_signed(Item *a) :Item_int_func(a) {} + const char *func_name() const { return "cast_as_signed"; } double val() { double tmp= args[0]->val(); @@ -242,6 +243,7 @@ class Item_func_unsigned :public Item_func_signed { public: Item_func_unsigned(Item *a) :Item_func_signed(a) {} + const char *func_name() const { return "cast_as_unsigned"; } void fix_length_and_dec() { max_length=args[0]->max_length; unsigned_flag=1; } void print(String *str); @@ -352,6 +354,7 @@ class Item_dec_func :public Item_real_func #ifndef HAVE_FINITE return value; #else + /* The following should be safe, even if we compare doubles */ if (finite(value) && value != POSTFIX_ERROR) return value; null_value=1; @@ -516,13 +519,13 @@ class Item_func_rand :public Item_real_func { struct rand_struct *rand; public: - Item_func_rand(Item *a) :Item_real_func(a) {} - Item_func_rand() :Item_real_func() {} + Item_func_rand(Item *a) :Item_real_func(a), rand(0) {} + Item_func_rand() :Item_real_func() {} double val(); const char *func_name() const { return "rand"; } bool const_item() const { return 0; } void update_used_tables(); - void fix_length_and_dec(); + bool fix_fields(THD *thd, struct st_table_list *tables, Item **ref); }; @@ -1039,6 +1042,7 @@ public: table_map not_null_tables() const { return 0; } bool fix_fields(THD *thd, struct st_table_list *tlist, Item **ref); bool eq(const Item *, bool binary_cmp) const; + /* The following should be safe, even if we compare doubles */ longlong val_int() { DBUG_ASSERT(fixed == 1); return val()!=0.0; } double val(); void print(String *str); diff --git a/sql/item_geofunc.cc b/sql/item_geofunc.cc index 9d58cc37c2a..935925c1e83 100644 --- a/sql/item_geofunc.cc +++ b/sql/item_geofunc.cc @@ -27,6 +27,13 @@ #include "sql_acl.h" #include <m_ctype.h> +void Item_geometry_func::fix_length_and_dec() +{ + collation.set(&my_charset_bin); + decimals=0; + max_length=MAX_BLOB_WIDTH; +} + String *Item_func_geometry_from_text::val_str(String *str) { @@ -44,6 +51,7 @@ String *Item_func_geometry_from_text::val_str(String *str) if ((arg_count == 2) && !args[1]->null_value) srid= (uint32)args[1]->val_int(); + str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) return 0; str->length(0); @@ -54,12 +62,6 @@ String *Item_func_geometry_from_text::val_str(String *str) } -void Item_func_geometry_from_text::fix_length_and_dec() -{ - max_length=MAX_BLOB_WIDTH; -} - - String *Item_func_geometry_from_wkb::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -71,6 +73,7 @@ String *Item_func_geometry_from_wkb::val_str(String *str) if ((arg_count == 2) && !args[1]->null_value) srid= (uint32)args[1]->val_int(); + str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) return 0; str->length(0); @@ -84,12 +87,6 @@ String *Item_func_geometry_from_wkb::val_str(String *str) } -void Item_func_geometry_from_wkb::fix_length_and_dec() -{ - max_length=MAX_BLOB_WIDTH; -} - - String *Item_func_as_wkt::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -138,12 +135,6 @@ String *Item_func_as_wkb::val_str(String *str) } -void Item_func_as_wkb::fix_length_and_dec() -{ - max_length= MAX_BLOB_WIDTH; -} - - String *Item_func_geometry_type::val_str(String *str) { DBUG_ASSERT(fixed == 1); @@ -180,6 +171,7 @@ String *Item_func_envelope::val_str(String *str) return 0; srid= uint4korr(swkb->ptr()); + str->set_charset(&my_charset_bin); str->length(0); if (str->reserve(SRID_SIZE, 512)) return 0; @@ -202,6 +194,7 @@ String *Item_func_centroid::val_str(String *str) swkb->length() - SRID_SIZE)))) return 0; + str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) return 0; str->length(0); @@ -232,6 +225,7 @@ String *Item_func_spatial_decomp::val_str(String *str) return 0; srid= uint4korr(swkb->ptr()); + str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) goto err; str->length(0); @@ -279,6 +273,7 @@ String *Item_func_spatial_decomp_n::val_str(String *str) swkb->length() - SRID_SIZE))))) return 0; + str->set_charset(&my_charset_bin); if (str->reserve(SRID_SIZE, 512)) goto err; srid= uint4korr(swkb->ptr()); @@ -333,6 +328,7 @@ String *Item_func_point::val_str(String *str) str->realloc(1 + 4 + SIZEOF_STORED_DOUBLE*2)))) return 0; + str->set_charset(&my_charset_bin); str->length(0); str->q_append((char)Geometry::wkb_ndr); str->q_append((uint32)Geometry::wkb_point); @@ -358,6 +354,7 @@ String *Item_func_spatial_collection::val_str(String *str) String arg_value; uint i; + str->set_charset(&my_charset_bin); str->length(0); if (str->reserve(1 + 4 + 4, 512)) goto err; diff --git a/sql/item_geofunc.h b/sql/item_geofunc.h index a1f36130152..79e4f804a04 100644 --- a/sql/item_geofunc.h +++ b/sql/item_geofunc.h @@ -23,24 +23,33 @@ #pragma interface /* gcc class implementation */ #endif -class Item_func_geometry_from_text: public Item_str_func +class Item_geometry_func: public Item_str_func { public: - Item_func_geometry_from_text(Item *a) :Item_str_func(a) {} - Item_func_geometry_from_text(Item *a, Item *srid) :Item_str_func(a, srid) {} + Item_geometry_func() :Item_str_func() {} + Item_geometry_func(Item *a) :Item_str_func(a) {} + Item_geometry_func(Item *a,Item *b) :Item_str_func(a,b) {} + Item_geometry_func(Item *a,Item *b,Item *c) :Item_str_func(a,b,c) {} + Item_geometry_func(List<Item> &list) :Item_str_func(list) {} + void fix_length_and_dec(); +}; + +class Item_func_geometry_from_text: public Item_geometry_func +{ +public: + Item_func_geometry_from_text(Item *a) :Item_geometry_func(a) {} + Item_func_geometry_from_text(Item *a, Item *srid) :Item_geometry_func(a, srid) {} const char *func_name() const { return "geometryfromtext"; } String *val_str(String *); - void fix_length_and_dec(); }; -class Item_func_geometry_from_wkb: public Item_str_func +class Item_func_geometry_from_wkb: public Item_geometry_func { public: - Item_func_geometry_from_wkb(Item *a): Item_str_func(a) {} - Item_func_geometry_from_wkb(Item *a, Item *srid): Item_str_func(a, srid) {} + Item_func_geometry_from_wkb(Item *a): Item_geometry_func(a) {} + Item_func_geometry_from_wkb(Item *a, Item *srid): Item_geometry_func(a, srid) {} const char *func_name() const { return "geometryfromwkb"; } String *val_str(String *); - void fix_length_and_dec(); }; class Item_func_as_wkt: public Item_str_func @@ -52,13 +61,12 @@ public: void fix_length_and_dec(); }; -class Item_func_as_wkb: public Item_str_func +class Item_func_as_wkb: public Item_geometry_func { public: - Item_func_as_wkb(Item *a): Item_str_func(a) {} + Item_func_as_wkb(Item *a): Item_geometry_func(a) {} const char *func_name() const { return "aswkb"; } String *val_str(String *); - void fix_length_and_dec(); }; class Item_func_geometry_type: public Item_str_func @@ -73,40 +81,37 @@ public: }; }; -class Item_func_centroid: public Item_str_func +class Item_func_centroid: public Item_geometry_func { public: - Item_func_centroid(Item *a): Item_str_func(a) {} + Item_func_centroid(Item *a): Item_geometry_func(a) {} const char *func_name() const { return "centroid"; } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} }; -class Item_func_envelope: public Item_str_func +class Item_func_envelope: public Item_geometry_func { public: - Item_func_envelope(Item *a): Item_str_func(a) {} + Item_func_envelope(Item *a): Item_geometry_func(a) {} const char *func_name() const { return "envelope"; } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} }; -class Item_func_point: public Item_str_func +class Item_func_point: public Item_geometry_func { public: - Item_func_point(Item *a, Item *b): Item_str_func(a, b) {} - Item_func_point(Item *a, Item *b, Item *srid): Item_str_func(a, b, srid) {} + Item_func_point(Item *a, Item *b): Item_geometry_func(a, b) {} + Item_func_point(Item *a, Item *b, Item *srid): Item_geometry_func(a, b, srid) {} const char *func_name() const { return "point"; } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} }; -class Item_func_spatial_decomp: public Item_str_func +class Item_func_spatial_decomp: public Item_geometry_func { enum Functype decomp_func; public: Item_func_spatial_decomp(Item *a, Item_func::Functype ft) : - Item_str_func(a) { decomp_func = ft; } + Item_geometry_func(a) { decomp_func = ft; } const char *func_name() const { switch (decomp_func) @@ -123,15 +128,14 @@ public: } } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} }; -class Item_func_spatial_decomp_n: public Item_str_func +class Item_func_spatial_decomp_n: public Item_geometry_func { enum Functype decomp_func_n; public: Item_func_spatial_decomp_n(Item *a, Item *b, Item_func::Functype ft): - Item_str_func(a, b) { decomp_func_n = ft; } + Item_geometry_func(a, b) { decomp_func_n = ft; } const char *func_name() const { switch (decomp_func_n) @@ -148,10 +152,9 @@ public: } } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} }; -class Item_func_spatial_collection: public Item_str_func +class Item_func_spatial_collection: public Item_geometry_func { String tmp_value; enum Geometry::wkbType coll_type; @@ -159,13 +162,12 @@ class Item_func_spatial_collection: public Item_str_func public: Item_func_spatial_collection( List<Item> &list, enum Geometry::wkbType ct, enum Geometry::wkbType it): - Item_str_func(list) + Item_geometry_func(list) { coll_type=ct; item_type=it; } String *val_str(String *); - void fix_length_and_dec(){max_length=MAX_BLOB_WIDTH;} const char *func_name() const { return "multipoint"; } }; diff --git a/sql/item_row.cc b/sql/item_row.cc index 8bf0d5061a7..8a020861fef 100644 --- a/sql/item_row.cc +++ b/sql/item_row.cc @@ -84,19 +84,21 @@ bool Item_row::fix_fields(THD *thd, TABLE_LIST *tabl, Item **ref) return 0; } -void Item_row::split_sum_func(Item **ref_pointer_array, List<Item> &fields) +void Item_row::split_sum_func(THD *thd, Item **ref_pointer_array, + List<Item> &fields) { Item **arg, **arg_end; for (arg= items, arg_end= items+arg_count; arg != arg_end ; arg++) { if ((*arg)->with_sum_func && (*arg)->type() != SUM_FUNC_ITEM) - (*arg)->split_sum_func(ref_pointer_array, fields); + (*arg)->split_sum_func(thd, ref_pointer_array, fields); else if ((*arg)->used_tables() || (*arg)->type() == SUM_FUNC_ITEM) { uint el= fields.elements; + Item *new_item= new Item_ref(ref_pointer_array + el, 0, (*arg)->name); fields.push_front(*arg); ref_pointer_array[el]= *arg; - *arg= new Item_ref(ref_pointer_array + el, arg, 0, (*arg)->name); + thd->change_item_tree(arg, new_item); } } } diff --git a/sql/item_row.h b/sql/item_row.h index ec5f0f1fc95..ef5856dcdd3 100644 --- a/sql/item_row.h +++ b/sql/item_row.h @@ -57,7 +57,7 @@ public: return 0; }; bool fix_fields(THD *thd, TABLE_LIST *tables, Item **ref); - void split_sum_func(Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); table_map used_tables() const { return used_tables_cache; }; bool const_item() const { return const_item_cache; }; enum Item_result result_type() const { return ROW_RESULT; } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index b3665edad39..d015ca36eac 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -289,7 +289,8 @@ String *Item_func_concat::val_str(String *str) str->copy(*res); str->append(*res2); } - res=str; + res= str; + use_as_buff= &tmp_value; } else if (res == &tmp_value) { @@ -531,7 +532,7 @@ String *Item_func_concat_ws::val_str(String *str) uint i; null_value=0; - if (!(sep_str= separator->val_str(&tmp_sep_str))) + if (!(sep_str= args[0]->val_str(&tmp_sep_str))) goto null; use_as_buff= &tmp_value; @@ -540,7 +541,7 @@ String *Item_func_concat_ws::val_str(String *str) // Skip until non-null argument is found. // If not, return the empty string - for (i=0; i < arg_count; i++) + for (i=1; i < arg_count; i++) if ((res= args[i]->val_str(str))) break; if (i == arg_count) @@ -634,67 +635,25 @@ null: return 0; } -void Item_func_concat_ws::split_sum_func(Item **ref_pointer_array, - List<Item> &fields) -{ - if (separator->with_sum_func && separator->type() != SUM_FUNC_ITEM) - separator->split_sum_func(ref_pointer_array, fields); - else if (separator->used_tables() || separator->type() == SUM_FUNC_ITEM) - { - uint el= fields.elements; - fields.push_front(separator); - ref_pointer_array[el]= separator; - separator= new Item_ref(ref_pointer_array + el, - &separator, 0, separator->name); - } - Item_str_func::split_sum_func(ref_pointer_array, fields); -} void Item_func_concat_ws::fix_length_and_dec() { - collation.set(separator->collation); - max_length=separator->max_length*(arg_count-1); - for (uint i=0 ; i < arg_count ; i++) - { - DTCollation tmp(collation.collation, collation.derivation); + max_length=0; + + if (agg_arg_collations(collation, args, arg_count)) + return; + + max_length= arg_count > 1 ? args[0]->max_length * (arg_count - 2) : 0; + for (uint i=1 ; i < arg_count ; i++) max_length+=args[i]->max_length; - if (collation.aggregate(args[i]->collation)) - { - collation.set(tmp); // Restore the previous value - my_coll_agg_error(collation, args[i]->collation, func_name()); - break; - } - } + if (max_length > MAX_BLOB_WIDTH) { max_length=MAX_BLOB_WIDTH; maybe_null=1; } - used_tables_cache|= separator->used_tables(); - not_null_tables_cache&= separator->not_null_tables(); - const_item_cache&= separator->const_item(); - with_sum_func= with_sum_func || separator->with_sum_func; } -void Item_func_concat_ws::update_used_tables() -{ - Item_func::update_used_tables(); - separator->update_used_tables(); - used_tables_cache|=separator->used_tables(); - const_item_cache&=separator->const_item(); -} - -void Item_func_concat_ws::print(String *str) -{ - str->append("concat_ws(", 10); - separator->print(str); - if (arg_count) - { - str->append(','); - print_args(str, 0); - } - str->append(')'); -} String *Item_func_reverse::val_str(String *str) { @@ -1244,7 +1203,7 @@ String *Item_func_ltrim::val_str(String *str) { const char *r_ptr=remove_str->ptr(); end-=remove_length; - while (ptr < end && !memcmp(ptr,r_ptr,remove_length)) + while (ptr <= end && !memcmp(ptr, r_ptr, remove_length)) ptr+=remove_length; end+=remove_length; } @@ -1316,8 +1275,8 @@ String *Item_func_rtrim::val_str(String *str) else #endif /* USE_MB */ { - while (ptr + remove_length < end && - !memcmp(end-remove_length,r_ptr,remove_length)) + while (ptr + remove_length <= end && + !memcmp(end-remove_length, r_ptr, remove_length)) end-=remove_length; } } @@ -1770,19 +1729,20 @@ String *Item_func_elt::val_str(String *str) } -void Item_func_make_set::split_sum_func(Item **ref_pointer_array, +void Item_func_make_set::split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields) { if (item->with_sum_func && item->type() != SUM_FUNC_ITEM) - item->split_sum_func(ref_pointer_array, fields); + item->split_sum_func(thd, ref_pointer_array, fields); else if (item->used_tables() || item->type() == SUM_FUNC_ITEM) { uint el= fields.elements; + Item *new_item= new Item_ref(ref_pointer_array + el, 0, item->name); fields.push_front(item); ref_pointer_array[el]= item; - item= new Item_ref(ref_pointer_array + el, &item, 0, item->name); + thd->change_item_tree(&item, new_item); } - Item_str_func::split_sum_func(ref_pointer_array, fields); + Item_str_func::split_sum_func(thd, ref_pointer_array, fields); } @@ -2424,14 +2384,22 @@ String *Item_load_file::val_str(String *str) String *file_name; File file; MY_STAT stat_info; + char path[FN_REFLEN]; DBUG_ENTER("load_file"); - if (!(file_name= args[0]->val_str(str)) || + if (!(file_name= args[0]->val_str(str)) #ifndef NO_EMBEDDED_ACCESS_CHECKS - !(current_thd->master_access & FILE_ACL) || + || !(current_thd->master_access & FILE_ACL) #endif - !my_stat(file_name->c_ptr(), &stat_info, MYF(MY_WME))) + ) goto err; + + (void) fn_format(path, file_name->c_ptr(), mysql_real_data_home, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME); + + if (!my_stat(path, &stat_info, MYF(MY_WME))) + goto err; + if (!(stat_info.st_mode & S_IROTH)) { /* my_error(ER_TEXTFILE_NOT_READABLE, MYF(0), file_name->c_ptr()); */ @@ -2711,35 +2679,40 @@ longlong Item_func_crc32::val_int() String *Item_func_compress::val_str(String *str) { + int err= Z_OK, code; + ulong new_size; + String *res; + Byte *body; + char *tmp, *last_char; DBUG_ASSERT(fixed == 1); - String *res= args[0]->val_str(str); - if (!res) + + if (!(res= args[0]->val_str(str))) { null_value= 1; return 0; } if (res->is_empty()) return res; - int err= Z_OK; - int code; - /* - citation from zlib.h (comment for compress function): + Citation from zlib.h (comment for compress function): Compresses the source buffer into the destination buffer. sourceLen is - the byte length of the source buffer. Upon entry, destLen is the total - size of the destination buffer, which must be at least 0.1% larger than - sourceLen plus 12 bytes. - - Proportion 120/100 founded by Sinisa with help of procedure - compress(compress(compress(...))) - I.e. zlib give number 'at least'.. + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. + We assume here that the buffer can't grow more than .25 %. */ - ulong new_size= (ulong)((res->length()*120)/100)+12; + new_size= res->length() + res->length() / 5 + 12; - buffer.realloc((uint32)new_size + 4 + 1); - Byte *body= ((Byte*)buffer.ptr()) + 4; + // Check new_size overflow: new_size <= res->length() + if (((uint32) (new_size+5) <= res->length()) || + buffer.realloc((uint32) new_size + 4 + 1)) + { + null_value= 1; + return 0; + } + body= ((Byte*)buffer.ptr()) + 4; // As far as we have checked res->is_empty() we can use ptr() if ((err= compress(body, &new_size, @@ -2751,11 +2724,11 @@ String *Item_func_compress::val_str(String *str) return 0; } - char *tmp= (char*)buffer.ptr(); // int4store is a macro; avoid side effects + tmp= (char*)buffer.ptr(); // int4store is a macro; avoid side effects int4store(tmp, res->length() & 0x3FFFFFFF); - /* This is for the stupid char fields which trim ' ': */ - char *last_char= ((char*)body)+new_size-1; + /* This is to ensure that things works for CHAR fields, which trim ' ': */ + last_char= ((char*)body)+new_size-1; if (*last_char == ' ') { *++last_char= '.'; diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 49429a55cac..afc4b9da0a1 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -89,30 +89,12 @@ public: class Item_func_concat_ws :public Item_str_func { - Item *separator; String tmp_value; - public: - Item_func_concat_ws(Item *a,List<Item> &list) - :Item_str_func(list),separator(a) {} + Item_func_concat_ws(List<Item> &list) :Item_str_func(list) {} String *val_str(String *); void fix_length_and_dec(); - void update_used_tables(); - bool fix_fields(THD *thd, TABLE_LIST *tlist, Item **ref) - { - DBUG_ASSERT(fixed == 0); - return (separator->fix_fields(thd, tlist, &separator) || - separator->check_cols(1) || - Item_func::fix_fields(thd, tlist, ref)); - } - void split_sum_func(Item **ref_pointer_array, List<Item> &fields); const char *func_name() const { return "concat_ws"; } - bool walk(Item_processor processor, byte *arg) - { - return separator->walk(processor, arg) || - Item_str_func::walk(processor, arg); - } - void print(String *str); }; class Item_func_reverse :public Item_str_func @@ -418,7 +400,7 @@ public: item->check_cols(1) || Item_func::fix_fields(thd, tlist, ref)); } - void split_sum_func(Item **ref_pointer_array, List<Item> &fields); + void split_sum_func(THD *thd, Item **ref_pointer_array, List<Item> &fields); void fix_length_and_dec(); void update_used_tables(); const char *func_name() const { return "make_set"; } diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 25bb2701101..bafca8acf0f 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -164,12 +164,7 @@ bool Item_subselect::fix_fields(THD *thd_param, TABLE_LIST *tables, Item **ref) thd->where= "checking transformed subquery"; if (!(*ref)->fixed) ret= (*ref)->fix_fields(thd, tables, ref); - // We can't substitute aggregate functions like "SELECT (max(i))" - if (substype() == SINGLEROW_SUBS && (*ref)->with_sum_func) - { - my_error(ER_INVALID_GROUP_FUNC_USE, MYF(0)); - return 1; - } + thd->where= save_where; return ret; } // Is it one field subselect? @@ -321,18 +316,12 @@ Item_singlerow_subselect::select_transformer(JOIN *join) return RES_OK; SELECT_LEX *select_lex= join->select_lex; - - /* Juggle with current arena only if we're in prepared statement prepare */ - DBUG_PRINT("TANSF:", ("thd %p, select_lex->join->thd: %s", - thd, select_lex->join->thd)); Item_arena *arena= thd->current_arena; - Item_arena backup; - if (arena->is_conventional()) - arena= 0; // For easier test - + if (!select_lex->master_unit()->first_select()->next_select() && !select_lex->table_list.elements && select_lex->item_list.elements == 1 && + !select_lex->item_list.head()->with_sum_func && /* We cant change name of Item_field or Item_ref, because it will prevent it's correct resolving, but we should save name of @@ -341,7 +330,13 @@ Item_singlerow_subselect::select_transformer(JOIN *join) TODO: solve above problem */ !(select_lex->item_list.head()->type() == FIELD_ITEM || - select_lex->item_list.head()->type() == REF_ITEM) + select_lex->item_list.head()->type() == REF_ITEM) && + /* + switch off this optimisation for prepare statement, + because we do not rollback this changes + TODO: make rollback for it, or special name resolving mode in 5.0. + */ + !arena->is_stmt_prepare() ) { @@ -363,9 +358,6 @@ Item_singlerow_subselect::select_transformer(JOIN *join) if (join->conds || join->having) { Item *cond; - if (arena) - thd->set_n_backup_item_arena(arena, &backup); - if (!join->having) cond= join->conds; else if (!join->conds) @@ -376,19 +368,16 @@ Item_singlerow_subselect::select_transformer(JOIN *join) if (!(substitution= new Item_func_if(cond, substitution, new Item_null()))) goto err; - if (arena) - thd->restore_backup_item_arena(arena, &backup); } return RES_REDUCE; } return RES_OK; err: - if (arena) - thd->restore_backup_item_arena(arena, &backup); return RES_ERROR; } + void Item_singlerow_subselect::store(uint i, Item *item) { row[i]->store(item); @@ -412,7 +401,13 @@ void Item_singlerow_subselect::fix_length_and_dec() engine->fix_length_and_dec(row); value= *row; } - maybe_null= engine->may_be_null(); + /* + If there are not tables in subquery then ability to have NULL value + depends on SELECT list (if single row subquery have tables then it + always can be NULL if there are not records fetched). + */ + if (engine->no_tables()) + maybe_null= engine->may_be_null(); } uint Item_singlerow_subselect::cols() @@ -650,10 +645,13 @@ String *Item_in_subselect::val_str(String *str) } +/* Rewrite a single-column IN/ALL/ANY subselect. */ + Item_subselect::trans_res Item_in_subselect::single_value_transformer(JOIN *join, Comp_creator *func) { + const char *save_where= thd->where; Item_subselect::trans_res result= RES_ERROR; DBUG_ENTER("Item_in_subselect::single_value_transformer"); @@ -672,12 +670,27 @@ Item_in_subselect::single_value_transformer(JOIN *join, else thd->set_n_backup_item_arena(arena, &backup); + /* + Check that the right part of the subselect contains no more than one + column. E.g. in SELECT 1 IN (SELECT * ..) the right part is (SELECT * ...) + */ if (select_lex->item_list.elements > 1) { my_error(ER_OPERAND_COLUMNS, MYF(0), 1); goto err; } + /* + If this is an ALL/ANY single-value subselect, try to rewrite it with + a MIN/MAX subselect. We can do that if a possible NULL result of the + subselect can be ignored. + E.g. SELECT * FROM t1 WHERE b > ANY (SELECT a FROM t2) can be rewritten + with SELECT * FROM t1 WHERE b > (SELECT MAX(a) FROM t2). + We can't check that this optimization is safe if it's not a top-level + item of the WHERE clause (e.g. because the WHERE clause can contain IS + NULL/IS NOT NULL functions). If so, we rewrite ALL/ANY with NOT EXISTS + later in this method. + */ if ((abort_on_null || (upper_not && upper_not->top_level())) && !select_lex->master_unit()->uncacheable && !func->eqne_op()) { @@ -769,7 +782,6 @@ Item_in_subselect::single_value_transformer(JOIN *join, we can use same item for all selects. */ expr= new Item_ref((Item**)optimizer->get_cache(), - NULL, (char *)"<no matter>", (char *)in_left_expr_name); @@ -777,7 +789,13 @@ Item_in_subselect::single_value_transformer(JOIN *join, } select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; - + /* + Add the left part of a subselect to a WHERE or HAVING clause of + the right part, e.g. SELECT 1 IN (SELECT a FROM t1) => + SELECT Item_in_optimizer(1, SELECT a FROM t1 WHERE a=1) + HAVING is used only if the right part contains a SUM function, a GROUP + BY or a HAVING clause. + */ if (join->having || select_lex->with_sum_func || select_lex->group_list.elements) { @@ -886,6 +904,7 @@ Item_in_subselect::single_value_transformer(JOIN *join, } ok: + thd->where= save_where; result= RES_OK; err: @@ -898,6 +917,7 @@ err: Item_subselect::trans_res Item_in_subselect::row_value_transformer(JOIN *join) { + const char *save_where= thd->where; Item *item= 0; SELECT_LEX *select_lex= join->select_lex; DBUG_ENTER("Item_in_subselect::row_value_transformer"); @@ -943,9 +963,6 @@ Item_in_subselect::row_value_transformer(JOIN *join) } select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; - select_lex->setup_ref_array(thd, - select_lex->order_list.elements + - select_lex->group_list.elements); { uint n= left_expr->cols(); List_iterator_fast<Item> li(select_lex->item_list); @@ -956,9 +973,7 @@ Item_in_subselect::row_value_transformer(JOIN *join) (char *) "<no matter>", (char *) "<list ref>"); func= - eq_creator.create(new Item_ref((*optimizer->get_cache())-> - addr(i), - NULL, + eq_creator.create(new Item_ref((*optimizer->get_cache())->addr(i), (char *)"<no matter>", (char *)in_left_expr_name), func); @@ -994,6 +1009,7 @@ Item_in_subselect::row_value_transformer(JOIN *join) if (join->conds->fix_fields(thd, join->tables_list, 0)) goto err; } + thd->where= save_where; if (arena) thd->restore_backup_item_arena(arena, &backup); DBUG_RETURN(RES_OK); @@ -1271,7 +1287,8 @@ int subselect_uniquesubquery_engine::exec() error= table->file->index_read(table->record[0], tab->ref.key_buff, tab->ref.key_length,HA_READ_KEY_EXACT); - if (error && error != HA_ERR_KEY_NOT_FOUND) + if (error && + error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) error= report_error(table, error); else { @@ -1323,7 +1340,8 @@ int subselect_indexsubquery_engine::exec() error= table->file->index_read(table->record[0], tab->ref.key_buff, tab->ref.key_length,HA_READ_KEY_EXACT); - if (error && error != HA_ERR_KEY_NOT_FOUND) + if (error && + error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) error= report_error(table, error); else { @@ -1547,3 +1565,58 @@ int subselect_uniquesubquery_engine::change_item(Item_subselect *si, DBUG_ASSERT(0); return -1; } + + +/* + Report about presence of tables in subquery + + SINOPSYS + subselect_single_select_engine::no_tables() + + RETURN + TRUE there are not tables used in subquery + FALSE there are some tables in subquery +*/ +bool subselect_single_select_engine::no_tables() +{ + return(select_lex->table_list.elements == 0); +} + + +/* + Report about presence of tables in subquery + + SINOPSYS + subselect_union_engine::no_tables() + + RETURN + TRUE there are not tables used in subquery + FALSE there are some tables in subquery +*/ +bool subselect_union_engine::no_tables() +{ + for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) + { + if (sl->table_list.elements) + return FALSE; + } + return TRUE; +} + + +/* + Report about presence of tables in subquery + + SINOPSYS + subselect_uniquesubquery_engine::no_tables() + + RETURN + TRUE there are not tables used in subquery + FALSE there are some tables in subquery +*/ + +bool subselect_uniquesubquery_engine::no_tables() +{ + /* returning value is correct, but this method should never be called */ + return 0; +} diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 4a325c4b224..6efb9052115 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -291,6 +291,7 @@ public: static table_map calc_const_tables(TABLE_LIST *); virtual void print(String *str)= 0; virtual int change_item(Item_subselect *si, select_subselect *result)= 0; + virtual bool no_tables()= 0; }; @@ -315,6 +316,7 @@ public: table_map upper_select_const_tables(); void print (String *str); int change_item(Item_subselect *si, select_subselect *result); + bool no_tables(); }; @@ -335,6 +337,7 @@ public: table_map upper_select_const_tables(); void print (String *str); int change_item(Item_subselect *si, select_subselect *result); + bool no_tables(); }; @@ -364,6 +367,7 @@ public: table_map upper_select_const_tables() { return 0; } void print (String *str); int change_item(Item_subselect *si, select_subselect *result); + bool no_tables(); }; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 85e27991ac6..743b3eee58c 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -24,7 +24,7 @@ #include "mysql_priv.h" Item_sum::Item_sum(List<Item> &list) - :args_copy(0), arg_count(list.elements) + :arg_count(list.elements) { if ((args=(Item**) sql_alloc(sizeof(Item*)*arg_count))) { @@ -56,39 +56,6 @@ Item_sum::Item_sum(THD *thd, Item_sum *item): if (!(args= (Item**) thd->alloc(sizeof(Item*)*arg_count))) return; memcpy(args, item->args, sizeof(Item*)*arg_count); - if (item->args_copy != 0) - save_args(thd); - else - args_copy= 0; -} - - -/* - Save copy of arguments if we are preparing a prepared statement - (arguments can be rewritten in get_tmp_table_item()) - - SYNOPSIS - Item_sum::save_args_for_prepared_statement() - thd - thread handler - - RETURN - 0 - OK - 1 - Error -*/ -bool Item_sum::save_args_for_prepared_statement(THD *thd) -{ - if (!thd->current_arena->is_conventional() && args_copy == 0) - return save_args(thd->current_arena); - return 0; -} - - -bool Item_sum::save_args(Item_arena* arena) -{ - if (!(args_copy= (Item**) arena->alloc(sizeof(Item*)*arg_count))) - return 1; - memcpy(args_copy, args, sizeof(Item*)*arg_count); - return 0; } @@ -99,17 +66,6 @@ void Item_sum::mark_as_sum_func() } -void Item_sum::cleanup() -{ - DBUG_ENTER("Item_sum::cleanup"); - Item_result_field::cleanup(); - if (args_copy != 0) - memcpy(args, args_copy, sizeof(Item*)*arg_count); - result_field=0; - DBUG_VOID_RETURN; -} - - void Item_sum::make_field(Send_field *tmp_field) { if (args[0]->type() == Item::FIELD_ITEM && keep_field_type()) @@ -215,9 +171,6 @@ Item_sum_num::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) { DBUG_ASSERT(fixed == 0); - if (save_args_for_prepared_statement(thd)) - return 1; - if (!thd->allow_sum_func) { my_error(ER_INVALID_GROUP_FUNC_USE,MYF(0)); @@ -249,9 +202,6 @@ Item_sum_hybrid::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) { DBUG_ASSERT(fixed == 0); - if (save_args_for_prepared_statement(thd)) - return 1; - Item *item= args[0]; if (!thd->allow_sum_func) { @@ -1855,8 +1805,7 @@ Item_func_group_concat::Item_func_group_concat(bool is_distinct, SQL_LIST *is_order, String *is_separator) :Item_sum(), tmp_table_param(0), max_elements_in_tree(0), warning(0), - warning_available(0), key_length(0), - tree_mode(0), distinct(is_distinct), warning_for_row(0), + key_length(0), tree_mode(0), distinct(is_distinct), warning_for_row(0), separator(is_separator), tree(&tree_base), table(0), order(0), tables_list(0), arg_count_order(0), arg_count_field(0), @@ -1914,7 +1863,6 @@ Item_func_group_concat::Item_func_group_concat(THD *thd, tmp_table_param(item->tmp_table_param), max_elements_in_tree(item->max_elements_in_tree), warning(item->warning), - warning_available(item->warning_available), key_length(item->key_length), tree_mode(item->tree_mode), distinct(item->distinct), @@ -1941,10 +1889,6 @@ void Item_func_group_concat::cleanup() DBUG_ENTER("Item_func_group_concat::cleanup"); Item_sum::cleanup(); - /* fix order list */ - for (uint i= 0; i < arg_count_order ; i++) - order[i]->item= &order[i]->item_ptr; - /* Free table and tree if they belong to this item (if item have not pointer to original item from which was made copy => it own its objects ) @@ -1964,6 +1908,13 @@ void Item_func_group_concat::cleanup() tree_mode= 0; delete_tree(tree); } + if (warning) + { + char warn_buff[MYSQL_ERRMSG_SIZE]; + sprintf(warn_buff, ER(ER_CUT_VALUE_GROUP_CONCAT), count_cut_values); + warning->set_msg(thd, warn_buff); + warning= 0; + } } DBUG_VOID_RETURN; } @@ -1971,19 +1922,6 @@ void Item_func_group_concat::cleanup() Item_func_group_concat::~Item_func_group_concat() { - /* - Free table and tree if they belong to this item (if item have not pointer - to original item from which was made copy => it own its objects ) - */ - if (!original) - { - if (warning_available) - { - char warn_buff[MYSQL_ERRMSG_SIZE]; - sprintf(warn_buff, ER(ER_CUT_VALUE_GROUP_CONCAT), count_cut_values); - warning->set_msg(current_thd, warn_buff); - } - } } @@ -2033,16 +1971,20 @@ bool Item_func_group_concat::add() } null_value= FALSE; + + TREE_ELEMENT *el= 0; // Only for safety if (tree_mode) - { - if (!tree_insert(tree, table->record[0], 0, tree->custom_arg)) - return 1; - } - else - { - if (result.length() <= group_concat_max_len && !warning_for_row) - dump_leaf_key(table->record[0], 1, this); - } + el= tree_insert(tree, table->record[0], 0, tree->custom_arg); + /* + If the row is not a duplicate (el->count == 1) + we can dump the row here in case of GROUP_CONCAT(DISTINCT...) + instead of doing tree traverse later. + */ + if (result.length() <= group_concat_max_len && + !warning_for_row && + (!tree_mode || (el->count == 1 && distinct && !arg_count_order))) + dump_leaf_key(table->record[0], 1, this); + return 0; } @@ -2060,9 +2002,6 @@ Item_func_group_concat::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) uint i; /* for loop variable */ DBUG_ASSERT(fixed == 0); - if (save_args_for_prepared_statement(thd)) - return 1; - if (!thd->allow_sum_func) { my_error(ER_INVALID_GROUP_FUNC_USE,MYF(0)); @@ -2091,6 +2030,8 @@ Item_func_group_concat::fix_fields(THD *thd, TABLE_LIST *tables, Item **ref) thd->allow_sum_func= 1; if (!(tmp_table_param= new TMP_TABLE_PARAM)) return 1; + /* We'll convert all blobs to varchar fields in the temporary table */ + tmp_table_param->convert_blob_length= group_concat_max_len; tables_list= tables; fixed= 1; return 0; @@ -2186,9 +2127,7 @@ bool Item_func_group_concat::setup(THD *thd) } else { - compare_key= NULL; - if (distinct) - compare_key= (qsort_cmp2) group_concat_key_cmp_with_distinct; + compare_key= (qsort_cmp2) group_concat_key_cmp_with_distinct; } /* Create a tree of sort. Tree is used for a sort and a remove double @@ -2231,17 +2170,17 @@ String* Item_func_group_concat::val_str(String* str) DBUG_ASSERT(fixed == 1); if (null_value) return 0; + if (count_cut_values && !warning) + warning= push_warning(item_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_CUT_VALUE_GROUP_CONCAT, + ER(ER_CUT_VALUE_GROUP_CONCAT)); + if (result.length()) + return &result; if (tree_mode) { tree_walk(tree, (tree_walk_action)&dump_leaf_key, (void*)this, left_root_right); } - if (count_cut_values && !warning_available) - { - warning_available= TRUE; - warning= push_warning(item_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CUT_VALUE_GROUP_CONCAT, NULL); - } return &result; } diff --git a/sql/item_sum.h b/sql/item_sum.h index 9046a215c86..cede5a1e02e 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -35,23 +35,22 @@ public: }; Item **args, *tmp_args[2]; - Item **args_copy; /* copy of arguments for PS */ uint arg_count; bool quick_group; /* If incremental update of fields */ void mark_as_sum_func(); - Item_sum() :args_copy(0), arg_count(0), quick_group(1) + Item_sum() :arg_count(0), quick_group(1) { mark_as_sum_func(); } Item_sum(Item *a) - :args(tmp_args), args_copy(0), arg_count(1), quick_group(1) + :args(tmp_args), arg_count(1), quick_group(1) { args[0]=a; mark_as_sum_func(); } Item_sum( Item *a, Item *b ) - :args(tmp_args), args_copy(0), arg_count(2), quick_group(1) + :args(tmp_args), arg_count(2), quick_group(1) { args[0]=a; args[1]=b; mark_as_sum_func(); @@ -59,7 +58,6 @@ public: Item_sum(List<Item> &list); //Copy constructor, need to perform subselects with temporary tables Item_sum(THD *thd, Item_sum *item); - void cleanup(); enum Type type() const { return SUM_FUNC_ITEM; } virtual enum Sumfunctype sum_func () const=0; inline bool reset() { clear(); return add(); }; @@ -94,8 +92,6 @@ public: virtual bool setup(THD *thd) {return 0;} virtual void make_unique() {} Item *get_tmp_table_item(THD *thd); - bool save_args_for_prepared_statement(THD *); - bool save_args(Item_arena *arena); bool walk (Item_processor processor, byte *argument); }; @@ -713,7 +709,6 @@ class Item_func_group_concat : public Item_sum TMP_TABLE_PARAM *tmp_table_param; uint max_elements_in_tree; MYSQL_ERROR *warning; - bool warning_available; uint key_length; bool tree_mode; bool distinct; diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 988529c845c..3a25921d84b 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -36,7 +36,7 @@ static const char *month_names[]= }; TYPELIB month_names_typelib= -{ array_elements(month_names)-1,"", month_names }; +{ array_elements(month_names)-1,"", month_names, NULL }; static const char *day_names[]= { @@ -45,7 +45,7 @@ static const char *day_names[]= }; TYPELIB day_names_typelib= -{ array_elements(day_names)-1,"", day_names}; +{ array_elements(day_names)-1,"", day_names, NULL}; /* @@ -989,7 +989,7 @@ longlong Item_func_unix_timestamp::val_int() { // Optimize timestamp field Field *field=((Item_field*) args[0])->field; if (field->type() == FIELD_TYPE_TIMESTAMP) - return ((Field_timestamp*) field)->get_timestamp(); + return ((Field_timestamp*) field)->get_timestamp(&null_value); } if (get_arg0_date(<ime, 0)) @@ -1298,14 +1298,13 @@ String *Item_func_curtime::val_str(String *str) void Item_func_curtime::fix_length_and_dec() { TIME ltime; - String tmp((char*) buff,sizeof(buff), &my_charset_bin); decimals=0; collation.set(&my_charset_bin); store_now_in_TIME(<ime); value= TIME_to_ulonglong_time(<ime); - make_time((DATE_TIME_FORMAT *) 0, <ime, &tmp); - max_length= buff_length= tmp.length(); + buff_length= (uint) my_time_to_str(<ime, buff); + max_length= buff_length; } @@ -1347,16 +1346,14 @@ String *Item_func_now::val_str(String *str) void Item_func_now::fix_length_and_dec() { - String tmp((char*) buff,sizeof(buff),&my_charset_bin); - decimals=0; collation.set(&my_charset_bin); store_now_in_TIME(<ime); value= (longlong) TIME_to_ulonglong_datetime(<ime); - make_datetime((DATE_TIME_FORMAT *) 0, <ime, &tmp); - max_length= buff_length= tmp.length(); + buff_length= (uint) my_datetime_to_str(<ime, buff); + max_length= buff_length; } @@ -2078,6 +2075,24 @@ bool Item_extract::eq(const Item *item, bool binary_cmp) const } +bool Item_char_typecast::eq(const Item *item, bool binary_cmp) const +{ + if (this == item) + return 1; + if (item->type() != FUNC_ITEM || + func_name() != ((Item_func*)item)->func_name()) + return 0; + + Item_char_typecast *cast= (Item_char_typecast*)item; + if (cast_length != cast->cast_length || + cast_cs != cast->cast_cs) + return 0; + + if (!args[0]->eq(cast->args[0], binary_cmp)) + return 0; + return 1; +} + void Item_typecast::print(String *str) { str->append("cast(", 5); diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 5f71045ef27..694f2dc583c 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -683,6 +683,8 @@ class Item_char_typecast :public Item_typecast public: Item_char_typecast(Item *a, int length_arg, CHARSET_INFO *cs_arg) :Item_typecast(a), cast_length(length_arg), cast_cs(cs_arg) {} + bool eq(const Item *item, bool binary_cmp) const; + const char *func_name() const { return "cast_as_char"; } const char* cast_type() const { return "char"; }; String *val_str(String *a); void fix_length_and_dec(); @@ -694,6 +696,7 @@ class Item_date_typecast :public Item_typecast_maybe_null { public: Item_date_typecast(Item *a) :Item_typecast_maybe_null(a) {} + const char *func_name() const { return "cast_as_date"; } String *val_str(String *str); bool get_date(TIME *ltime, uint fuzzy_date); const char *cast_type() const { return "date"; } @@ -709,6 +712,7 @@ class Item_time_typecast :public Item_typecast_maybe_null { public: Item_time_typecast(Item *a) :Item_typecast_maybe_null(a) {} + const char *func_name() const { return "cast_as_time"; } String *val_str(String *str); bool get_time(TIME *ltime); const char *cast_type() const { return "time"; } @@ -724,6 +728,7 @@ class Item_datetime_typecast :public Item_typecast_maybe_null { public: Item_datetime_typecast(Item *a) :Item_typecast_maybe_null(a) {} + const char *func_name() const { return "cast_as_datetime"; } String *val_str(String *str); const char *cast_type() const { return "datetime"; } enum_field_types field_type() const { return MYSQL_TYPE_DATETIME; } diff --git a/sql/lock.cc b/sql/lock.cc index debfb900c23..6d5ca5bf6b4 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -794,9 +794,15 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, { if (thd->global_read_lock) // This thread had the read locks { - my_error(ER_CANT_UPDATE_WITH_READLOCK,MYF(0)); + if (is_not_commit) + my_error(ER_CANT_UPDATE_WITH_READLOCK,MYF(0)); (void) pthread_mutex_unlock(&LOCK_open); - DBUG_RETURN(1); + /* + We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. + This allowance is needed to not break existing versions of innobackup + which do a BEGIN; INSERT; FLUSH TABLES WITH READ LOCK; COMMIT. + */ + DBUG_RETURN(is_not_commit); } old_message=thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for release of readlock"); diff --git a/sql/log.cc b/sql/log.cc index ab080366f95..ef538c3b03f 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1929,19 +1929,6 @@ void MYSQL_LOG::set_max_size(ulong max_size_arg) } -Disable_binlog::Disable_binlog(THD *thd_arg) : - thd(thd_arg), save_options(thd_arg->options) -{ - thd_arg->options&= ~OPTION_BIN_LOG; -} - - -Disable_binlog::~Disable_binlog() -{ - thd->options= save_options; -} - - /* Check if a string is a valid number @@ -2005,15 +1992,15 @@ void print_buffer_to_file(enum loglevel level, const char *buffer) skr=time(NULL); localtime_r(&skr, &tm_tmp); start=&tm_tmp; - fprintf(stderr, "%02d%02d%02d %2d:%02d:%02d [%s] %s\n", - start->tm_year % 100, - start->tm_mon+1, - start->tm_mday, - start->tm_hour, - start->tm_min, - start->tm_sec, + fprintf(stderr, "%02d%02d%02d %2d:%02d:%02d [%s] %s\n", + start->tm_year % 100, + start->tm_mon+1, + start->tm_mday, + start->tm_hour, + start->tm_min, + start->tm_sec, (level == ERROR_LEVEL ? "ERROR" : level == WARNING_LEVEL ? - "WARNING" : "INFORMATION"), + "Warning" : "Note"), buffer); fflush(stderr); diff --git a/sql/log_event.cc b/sql/log_event.cc index c2a684ffe03..a80b0e13df8 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1362,7 +1362,7 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli) Thank you. */ thd->catalog= (char*) catalog; - thd->db= (char*) rewrite_db(db); + thd->db= (char*) rewrite_db(db); // thd->db_length is set later if needed thd->variables.auto_increment_increment= auto_increment_increment; thd->variables.auto_increment_offset= auto_increment_offset; @@ -1384,6 +1384,11 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli) if (db_ok(thd->db, replicate_do_db, replicate_ignore_db)) { thd->set_time((time_t)when); + /* + We cannot use db_len from event to fill thd->db_length, because + rewrite_db() may have changed db. + */ + thd->db_length= thd->db ? strlen(thd->db) : 0; thd->query_length= q_len; thd->query = (char*)query; VOID(pthread_mutex_lock(&LOCK_thread_count)); @@ -1527,11 +1532,19 @@ end: */ thd->db= thd->catalog= 0; // prevent db from being freed thd->query= 0; // just to be sure - thd->query_length= 0; + thd->query_length= thd->db_length =0; VOID(pthread_mutex_unlock(&LOCK_thread_count)); close_thread_tables(thd); free_root(&thd->mem_root,MYF(MY_KEEP_PREALLOC)); - return (thd->query_error ? thd->query_error : Log_event::exec_event(rli)); + /* + If there was an error we stop. Otherwise we increment positions. Note that + we will not increment group* positions if we are just after a SET + ONE_SHOT, because SET ONE_SHOT should not be separated from its following + updating query. + */ + return (thd->query_error ? thd->query_error : + (thd->one_shot_set ? (rli->inc_event_relay_log_pos(get_event_len()),0) : + Log_event::exec_event(rli))); } #endif @@ -2422,7 +2435,7 @@ int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli, bool use_rli_only_for_errors) { char *load_data_query= 0; - thd->db= (char*) rewrite_db(db); + thd->db= (char*) rewrite_db(db); // thd->db_length is set later if needed DBUG_ASSERT(thd->query == 0); thd->query_length= 0; // Should not be needed thd->query_error= 0; @@ -2454,6 +2467,7 @@ int Load_log_event::exec_event(NET* net, struct st_relay_log_info* rli, if (db_ok(thd->db, replicate_do_db, replicate_ignore_db)) { thd->set_time((time_t)when); + thd->db_length= thd->db ? strlen(thd->db) : 0; VOID(pthread_mutex_lock(&LOCK_thread_count)); thd->query_id = query_id++; VOID(pthread_mutex_unlock(&LOCK_thread_count)); @@ -2580,7 +2594,7 @@ Slave: load data infile on table '%s' at log position %s in log \ VOID(pthread_mutex_lock(&LOCK_thread_count)); thd->db= thd->catalog= 0; thd->query= 0; - thd->query_length= 0; + thd->query_length= thd->db_length= 0; VOID(pthread_mutex_unlock(&LOCK_thread_count)); close_thread_tables(thd); if (load_data_query) diff --git a/sql/log_event.h b/sql/log_event.h index a9d18c65f0c..a1c02b0e3c0 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -755,7 +755,7 @@ public: { fname= afname; fname_len= alen; - local_fname= true; + local_fname= TRUE; } /* fname doesn't point to memory inside Log_event::temp_buf */ int check_fname_outside_temp_buf() diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index d8916149b77..a339b61563a 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -331,14 +331,6 @@ void debug_sync_point(const char* lock_name, uint lock_timeout); #define WEEK_MONDAY_FIRST 1 #define WEEK_YEAR 2 #define WEEK_FIRST_WEEKDAY 4 -/* - Required buffer length for make_date, make_time, make_datetime - and TIME_to_string functions. Note, that the caller is still - responsible to check that given TIME structure has values - in valid ranges, otherwise size of the buffer could be not - enough. -*/ -#define MAX_DATE_REP_LENGTH 30 enum enum_parsing_place { @@ -426,7 +418,7 @@ int mysql_insert_select_prepare(THD *thd); int insert_select_precheck(THD *thd, TABLE_LIST *tables); int update_precheck(THD *thd, TABLE_LIST *tables); int delete_precheck(THD *thd, TABLE_LIST *tables); -int insert_precheck(THD *thd, TABLE_LIST *tables, bool update); +int insert_precheck(THD *thd, TABLE_LIST *tables); int create_table_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *create_table); Item *negate_expression(THD *thd, Item *expr); @@ -442,6 +434,9 @@ struct Query_cache_query_flags uint collation_connection_num; ha_rows limit; Time_zone *time_zone; + ulong sql_mode; + ulong max_sort_length; + ulong group_concat_max_len; }; #define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags) #include "sql_cache.h" @@ -496,6 +491,7 @@ bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length); bool is_update_query(enum enum_sql_command command); bool alloc_query(THD *thd, char *packet, ulong packet_length); void mysql_init_select(LEX *lex); +void mysql_reset_thd_for_next_command(THD *thd); void mysql_init_query(THD *thd, uchar *buf, uint length, bool lexonly=0); bool mysql_new_select(LEX *lex, bool move_down); void create_select_for_variable(const char *var_name); @@ -571,7 +567,7 @@ int mysql_union(THD *thd, LEX *lex, select_result *result, int mysql_handle_derived(LEX *lex); Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, Item ***copy_func, Field **from_field, - bool group,bool modify_item); + bool group, bool modify_item, uint convert_blob_length); int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, List<create_field> &fields, List<Key> &keys, uint &db_options, @@ -697,6 +693,7 @@ int mysqld_show_keys(THD *thd, TABLE_LIST *table); int mysqld_show_logs(THD *thd); void append_identifier(THD *thd, String *packet, const char *name, uint length); +int get_quote_char_for_identifier(THD *thd, const char *name, uint length); void mysqld_list_fields(THD *thd,TABLE_LIST *table, const char *wild); int mysqld_dump_create_info(THD *thd, TABLE *table, int fd = -1); int mysqld_show_create(THD *thd, TABLE_LIST *table_list); @@ -739,14 +736,18 @@ void mysql_reset_errors(THD *thd); my_bool mysqld_show_warnings(THD *thd, ulong levels_to_show); /* sql_handler.cc */ -int mysql_ha_open(THD *thd, TABLE_LIST *tables); -int mysql_ha_close(THD *thd, TABLE_LIST *tables, - bool dont_send_ok=0, bool dont_lock=0, bool no_alias=0); -int mysql_ha_close_list(THD *thd, TABLE_LIST *tables, bool flushed=0); +int mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen= 0); +int mysql_ha_close(THD *thd, TABLE_LIST *tables); int mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows); +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags); +/* mysql_ha_flush mode_flags bits */ +#define MYSQL_HA_CLOSE_FINAL 0x00 +#define MYSQL_HA_REOPEN_ON_USAGE 0x01 +#define MYSQL_HA_FLUSH_ALL 0x02 /* sql_base.cc */ +#define TMP_TABLE_KEY_EXTRA 8 void set_item_name(Item *item,char *pos,uint length); bool add_field_to_list(THD *thd, char *field_name, enum enum_field_types type, char *length, char *decimal, @@ -769,7 +770,8 @@ enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND, IGNORE_ERRORS}; extern const Item **not_found_item; Item ** find_item_in_list(Item *item, List<Item> &items, uint *counter, - find_item_error_report_type report_error); + find_item_error_report_type report_error, + bool *unaliased); bool get_key_map_from_key_list(key_map *map, TABLE *table, List<String> *index_list); bool insert_fields(THD *thd,TABLE_LIST *tables, @@ -789,6 +791,7 @@ void wait_for_refresh(THD *thd); int open_tables(THD *thd, TABLE_LIST *tables, uint *counter); int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables); int open_and_lock_tables(THD *thd,TABLE_LIST *tables); +void relink_tables_for_derived(THD *thd); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); @@ -896,9 +899,10 @@ extern void yyerror(const char*); extern bool check_reserved_words(LEX_STRING *name); /* strfunc.cc */ -ulonglong find_set(TYPELIB *typelib,const char *x, uint length, +ulonglong find_set(TYPELIB *lib, const char *x, uint length, CHARSET_INFO *cs, char **err_pos, uint *err_len, bool *set_warning); uint find_type(TYPELIB *lib, const char *find, uint length, bool part_match); +uint find_type2(TYPELIB *lib, const char *find, uint length, CHARSET_INFO *cs); uint check_word(TYPELIB *lib, const char *val, const char *end, const char **end_of_word); @@ -969,7 +973,7 @@ extern uint test_flags,select_errors,ha_open_options; extern uint protocol_version, mysqld_port, dropping_tables; extern uint delay_key_write_options, lower_case_table_names; extern bool opt_endinfo, using_udf_functions, locked_in_memory; -extern bool opt_using_transactions, mysql_embedded; +extern bool opt_using_transactions, mysqld_embedded; extern bool using_update_log, opt_large_files, server_id_supplied; extern bool opt_log, opt_update_log, opt_bin_log, opt_slow_log, opt_error_log; extern bool opt_disable_networking, opt_skip_show_db; @@ -1085,8 +1089,6 @@ int openfrm(THD *thd, const char *name,const char *alias,uint filestat, uint prgflag, uint ha_open_flags, TABLE *outparam); int readfrm(const char *name, const void** data, uint* length); int writefrm(const char* name, const void* data, uint len); -int create_table_from_handler(const char *db, const char *name, - bool create_if_found); int closefrm(TABLE *table); db_type get_table_type(const char *name); int read_string(File file, gptr *to, uint length); @@ -1123,7 +1125,6 @@ void make_date(const DATE_TIME_FORMAT *format, const TIME *l_time, String *str); void make_time(const DATE_TIME_FORMAT *format, const TIME *l_time, String *str); -void TIME_to_string(const TIME *time, String *str); ulonglong TIME_to_ulonglong_datetime(const TIME *time); ulonglong TIME_to_ulonglong_date(const TIME *time); ulonglong TIME_to_ulonglong_time(const TIME *time); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index db81b14c9c9..3e5a999fbfa 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -225,7 +225,7 @@ const char *sql_mode_names[] = NullS }; TYPELIB sql_mode_typelib= { array_elements(sql_mode_names)-1,"", - sql_mode_names }; + sql_mode_names, NULL }; const char *first_keyword= "first", *binary_keyword= "BINARY"; const char *my_localhost= "localhost", *delayed_user= "DELAYED"; #if SIZEOF_OFF_T > 4 && defined(BIG_TABLES) @@ -327,6 +327,7 @@ const char *opt_date_time_formats[3]; char *language_ptr, *default_collation_name, *default_character_set_name; char mysql_data_home_buff[2], *mysql_data_home=mysql_real_data_home; +struct passwd *user_info; char server_version[SERVER_VERSION_LENGTH]; char *mysqld_unix_port, *opt_mysql_tmpdir; char *my_bind_addr_str; @@ -449,9 +450,9 @@ pthread_cond_t eventShutdown; #endif #ifndef EMBEDDED_LIBRARY -bool mysql_embedded=0; +bool mysqld_embedded=0; #else -bool mysql_embedded=1; +bool mysqld_embedded=1; #endif #ifndef DBUG_OFF @@ -965,7 +966,7 @@ void clean_up(bool print_message) if (print_message && errmesg) sql_print_information(ER(ER_SHUTDOWN_COMPLETE),my_progname); -#if !defined(__WIN__) && !defined(EMBEDDED_LIBRARY) +#if !defined(EMBEDDED_LIBRARY) if (!opt_bootstrap) (void) my_delete(pidfile_name,MYF(0)); // This may not always exist #endif @@ -1050,65 +1051,72 @@ static void set_ports() #ifndef EMBEDDED_LIBRARY /* Change to run as another user if started with --user */ -static void set_user(const char *user) +static struct passwd *check_user(const char *user) { #if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) - struct passwd *ent; + struct passwd *user_info; uid_t user_id= geteuid(); - // don't bother if we aren't superuser + // Don't bother if we aren't superuser if (user_id) { if (user) { - /* Don't give a warning, if real user is same as given with --user */ - struct passwd *user_info= getpwnam(user); + // Don't give a warning, if real user is same as given with --user + user_info= getpwnam(user); if ((!user_info || user_id != user_info->pw_uid) && global_system_variables.log_warnings) sql_print_warning( "One can only use the --user switch if running as root\n"); } - return; + return NULL; } if (!user) { if (!opt_bootstrap) { - fprintf(stderr,"Fatal error: Please read \"Security\" section of the manual to find out how to run mysqld as root!\n"); + sql_print_error("Fatal error: Please read \"Security\" section of the manual to find out how to run mysqld as root!\n"); unireg_abort(1); } - return; + return NULL; } if (!strcmp(user,"root")) - return; // Avoid problem with dynamic libraries + return NULL; // Avoid problem with dynamic libraries - uid_t uid; - if (!(ent = getpwnam(user))) + if (!(user_info= getpwnam(user))) { - // allow a numeric uid to be used + // Allow a numeric uid to be used const char *pos; - for (pos=user; my_isdigit(mysqld_charset,*pos); pos++) ; - if (*pos) // Not numeric id - { - fprintf(stderr,"Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n",user); - unireg_abort(1); - } - uid=atoi(user); // Use numberic uid + for (pos= user; my_isdigit(mysqld_charset,*pos); pos++) ; + if (*pos) // Not numeric id + goto err; + if (!(user_info= getpwuid(atoi(user)))) + goto err; + else + return user_info; } else - { + return user_info; + +err: + sql_print_error("Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n",user); +#endif + return NULL; +} + +static void set_user(const char *user, struct passwd *user_info) +{ +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + DBUG_ASSERT(user_info); #ifdef HAVE_INITGROUPS - initgroups((char*) user,ent->pw_gid); + initgroups((char*) user,user_info->pw_gid); #endif - if (setgid(ent->pw_gid) == -1) - { - sql_perror("setgid"); - unireg_abort(1); - } - uid=ent->pw_uid; + if (setgid(user_info->pw_gid) == -1) + { + sql_perror("setgid"); + unireg_abort(1); } - - if (setuid(uid) == -1) + if (setuid(user_info->pw_uid) == -1) { sql_perror("setuid"); unireg_abort(1); @@ -1116,6 +1124,25 @@ static void set_user(const char *user) #endif } + +static void set_effective_user(struct passwd *user_info) +{ +#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__) + DBUG_ASSERT(user_info); + if (setregid((gid_t)-1, user_info->pw_gid) == -1) + { + sql_perror("setregid"); + unireg_abort(1); + } + if (setreuid((uid_t)-1, user_info->pw_uid) == -1) + { + sql_perror("setreuid"); + unireg_abort(1); + } +#endif +} + + /* Change root user if started with --chroot */ static void set_root(const char *path) @@ -1191,7 +1218,16 @@ static void server_init(void) unireg_abort(1); } } - set_user(mysqld_user); // Works also with mysqld_user==NULL + + if ((user_info= check_user(mysqld_user))) + { +#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) + if (locked_in_memory) // getuid() == 0 here + set_effective_user(user_info); + else +#endif + set_user(mysqld_user, user_info); + } #ifdef __NT__ /* create named pipe */ @@ -1467,7 +1503,11 @@ static void init_signals(void) } static void start_signal_handler(void) -{} +{ + // Save vm id of this process + if (!opt_bootstrap) + create_pid_file(); +} static void check_data_home(const char *path) {} @@ -2085,8 +2125,7 @@ static void check_data_home(const char *path) /* ARGSUSED */ -extern "C" int my_message_sql(uint error, const char *str, - myf MyFlags __attribute__((unused))) +extern "C" int my_message_sql(uint error, const char *str, myf MyFlags) { THD *thd; DBUG_ENTER("my_message_sql"); @@ -2105,7 +2144,11 @@ extern "C" int my_message_sql(uint error, const char *str, if (thd->lex->current_select && thd->lex->current_select->no_error && !thd->is_fatal_error) { - DBUG_PRINT("error", ("above error converted to warning")); + DBUG_PRINT("error", ("Error converted to warning: current_select: no_error %d fatal_error: %d", + (thd->lex->current_select ? + thd->lex->current_select->no_error : 0), + (int) thd->is_fatal_error)); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, error, str); } else @@ -2119,7 +2162,7 @@ extern "C" int my_message_sql(uint error, const char *str, } } } - else + if (!thd || MyFlags & ME_NOREFRESH) sql_print_error("%s: %s",my_progname,str); /* purecov: inspected */ DBUG_RETURN(0); } @@ -2672,19 +2715,26 @@ server."); /* We must set dflt_key_cache in case we are using ISAM tables */ dflt_key_cache= sql_key_cache; -#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) - if (locked_in_memory && !geteuid()) +#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) && !defined(EMBEDDED_LIBRARY) + if (locked_in_memory && !getuid()) { + if (setreuid((uid_t)-1, 0) == -1) + { // this should never happen + sql_perror("setreuid"); + unireg_abort(1); + } if (mlockall(MCL_CURRENT)) { if (global_system_variables.log_warnings) sql_print_warning("Failed to lock memory. Errno: %d\n",errno); locked_in_memory= 0; } + if (user_info) + set_user(mysqld_user, user_info); } -#else - locked_in_memory=0; + else #endif + locked_in_memory=0; ft_init_stopwords(); @@ -2945,10 +2995,10 @@ we force server id to 2, but this MySQL server will not act as a slave."); #ifndef __NETWARE__ (void) pthread_kill(signal_thread, MYSQL_KILL_SIGNAL); #endif /* __NETWARE__ */ -#ifndef __WIN__ + if (!opt_bootstrap) (void) my_delete(pidfile_name,MYF(MY_WME)); // Not needed anymore -#endif + if (unix_sock != INVALID_SOCKET) unlink(mysqld_unix_port); exit(1); @@ -4036,13 +4086,16 @@ enum options_mysqld OPT_INNODB_BUFFER_POOL_SIZE, OPT_INNODB_BUFFER_POOL_AWE_MEM_MB, OPT_INNODB_ADDITIONAL_MEM_POOL_SIZE, + OPT_INNODB_MAX_PURGE_LAG, OPT_INNODB_FILE_IO_THREADS, OPT_INNODB_LOCK_WAIT_TIMEOUT, OPT_INNODB_THREAD_CONCURRENCY, OPT_INNODB_FORCE_RECOVERY, OPT_INNODB_STATUS_FILE, OPT_INNODB_MAX_DIRTY_PAGES_PCT, + OPT_INNODB_TABLE_LOCKS, OPT_INNODB_OPEN_FILES, + OPT_INNODB_AUTOEXTEND_INCREMENT, OPT_BDB_CACHE_SIZE, OPT_BDB_LOG_BUFFER_SIZE, OPT_BDB_MAX_LOCK, @@ -4284,10 +4337,20 @@ Disable with --skip-innodb (will save memory).", {"innodb_max_dirty_pages_pct", OPT_INNODB_MAX_DIRTY_PAGES_PCT, "Percentage of dirty pages allowed in bufferpool.", (gptr*) &srv_max_buf_pool_modified_pct, (gptr*) &srv_max_buf_pool_modified_pct, 0, GET_ULONG, REQUIRED_ARG, 90, 0, 100, 0, 0, 0}, + {"innodb_max_purge_lag", OPT_INNODB_MAX_PURGE_LAG, + "", + (gptr*) &srv_max_purge_lag, + (gptr*) &srv_max_purge_lag, 0, GET_LONG, REQUIRED_ARG, 0, 0, ~0L, + 0, 1L, 0}, {"innodb_status_file", OPT_INNODB_STATUS_FILE, "Enable SHOW INNODB STATUS output in the innodb_status.<pid> file", (gptr*) &innobase_create_status_file, (gptr*) &innobase_create_status_file, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"innodb_table_locks", OPT_INNODB_TABLE_LOCKS, + "If Innodb should enforce LOCK TABLE", + (gptr*) &global_system_variables.innodb_table_locks, + (gptr*) &global_system_variables.innodb_table_locks, + 0, GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, 0}, #endif /* End HAVE_INNOBASE_DB */ {"isam", OPT_ISAM, "Enable ISAM (if this version of MySQL supports it). \ Disable with --skip-isam.", @@ -4764,6 +4827,11 @@ log and this option does nothing anymore.", (gptr*) &innobase_additional_mem_pool_size, (gptr*) &innobase_additional_mem_pool_size, 0, GET_LONG, REQUIRED_ARG, 1*1024*1024L, 512*1024L, ~0L, 0, 1024, 0}, + {"innodb_autoextend_increment", OPT_INNODB_AUTOEXTEND_INCREMENT, + "Data file autoextend increment in megabytes", + (gptr*) &srv_auto_extend_increment, + (gptr*) &srv_auto_extend_increment, + 0, GET_LONG, REQUIRED_ARG, 8L, 1L, ~0L, 0, 1L, 0}, {"innodb_buffer_pool_awe_mem_mb", OPT_INNODB_BUFFER_POOL_AWE_MEM_MB, "If Windows AWE is used, the size of InnoDB buffer pool allocated from the AWE memory.", (gptr*) &innobase_buffer_pool_awe_mem_mb, (gptr*) &innobase_buffer_pool_awe_mem_mb, 0, @@ -4846,7 +4914,7 @@ log and this option does nothing anymore.", (gptr*) &dflt_key_cache_var.param_buff_size, (gptr*) 0, 0, (GET_ULL | GET_ASK_ADDR), - REQUIRED_ARG, KEY_CACHE_SIZE, MALLOC_OVERHEAD, (long) ~0, MALLOC_OVERHEAD, + REQUIRED_ARG, KEY_CACHE_SIZE, MALLOC_OVERHEAD, UINT_MAX32, MALLOC_OVERHEAD, IO_SIZE, 0}, {"key_cache_age_threshold", OPT_KEY_CACHE_AGE_THRESHOLD, "This characterizes the number of hits a hot block has to be untouched until it is considered aged enough to be downgraded to a warm block. This specifies the percentage ratio of that number of hits to the total number of blocks in key cache", diff --git a/sql/net_serv.cc b/sql/net_serv.cc index 82a3b1bd520..27311cb5613 100644 --- a/sql/net_serv.cc +++ b/sql/net_serv.cc @@ -193,9 +193,7 @@ my_bool net_realloc(NET *net, ulong length) { net->error= 1; net->report_error= 1; -#ifdef MYSQL_SERVER net->last_errno= ER_OUT_OF_RESOURCES; -#endif DBUG_RETURN(1); } net->buff=net->write_pos=buff; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 683bf1bdc13..10b9fc9fbfd 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -712,7 +712,7 @@ QUICK_SELECT_I::QUICK_SELECT_I() QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr, bool no_alloc, MEM_ROOT *parent_alloc) - :dont_free(0),error(0),free_file(0),cur_range(NULL),range(0) + :dont_free(0),sorted(0),error(0),free_file(0),cur_range(NULL),range(0) { index= key_nr; head= table; @@ -1725,9 +1725,10 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, } if (tree) - {/* - It is possible to use a range-based quick select (but it might be slower - than 'all' table scan). + { + /* + It is possible to use a range-based quick select (but it might be + slower than 'all' table scan). */ if (tree->merges.is_empty()) { @@ -4839,7 +4840,7 @@ void SEL_ARG::test_use_count(SEL_ARG *root) uint e_count=0; if (this == root && use_count != 1) { - sql_print_error("Note: Use_count: Wrong count %lu for root",use_count); + sql_print_information("Use_count: Wrong count %lu for root",use_count); return; } if (this->type != SEL_ARG::KEY_RANGE) @@ -4852,7 +4853,7 @@ void SEL_ARG::test_use_count(SEL_ARG *root) ulong count=count_key_part_usage(root,pos->next_key_part); if (count > pos->next_key_part->use_count) { - sql_print_error("Note: Use_count: Wrong count for key at 0x%lx, %lu should be %lu", + sql_print_information("Use_count: Wrong count for key at 0x%lx, %lu should be %lu", pos,pos->next_key_part->use_count,count); return; } @@ -4860,7 +4861,7 @@ void SEL_ARG::test_use_count(SEL_ARG *root) } } if (e_count != elements) - sql_print_error("Warning: Wrong use count: %u (should be %u) for tree at 0x%lx", + sql_print_warning("Wrong use count: %u (should be %u) for tree at 0x%lx", e_count, elements, (gptr) this); } @@ -5233,8 +5234,6 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree, QUICK_RANGE_SELECT *quick; DBUG_ENTER("get_quick_select"); - - if (param->table->key_info[param->real_keynr[idx]].flags & HA_SPATIAL) quick=new QUICK_RANGE_SELECT_GEOM(param->thd, param->table, param->real_keynr[idx], @@ -5243,7 +5242,7 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree, else quick=new QUICK_RANGE_SELECT(param->thd, param->table, param->real_keynr[idx], - test(parent_alloc), parent_alloc); + test(parent_alloc)); if (quick) { @@ -5370,7 +5369,6 @@ get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key, if (insert_dynamic(&quick->ranges, (gptr)&range)) return 1; - end: if (key_tree->right != &null_element) return get_quick_keys(param,quick,key,key_tree->right, @@ -5455,14 +5453,18 @@ bool QUICK_ROR_UNION_SELECT::check_if_keys_used(List<Item> *fields) return 0; } + /**************************************************************************** Create a QUICK RANGE based on a key + This allocates things in a new memory root, as this may be called many times + during a query. ****************************************************************************/ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, TABLE_REF *ref) { - QUICK_RANGE_SELECT *quick=new QUICK_RANGE_SELECT(thd, table, ref->key, 1); + MEM_ROOT *old_root= my_pthread_getspecific_ptr(MEM_ROOT*, THR_MALLOC); + QUICK_RANGE_SELECT *quick= new QUICK_RANGE_SELECT(thd, table, ref->key, 0); KEY *key_info = &table->key_info[ref->key]; KEY_PART *key_part; QUICK_RANGE *range; @@ -5473,17 +5475,12 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, if (quick->init()) { delete quick; - return 0; - } - - if (cp_buffer_from_ref(ref)) - { - if (thd->is_fatal_error) - goto err; // out of memory + goto err; } - if (!(range= new QUICK_RANGE())) - goto err; // out of memory + if (cp_buffer_from_ref(ref) && thd->is_fatal_error || + !(range= new QUICK_RANGE())) + goto err; // out of memory range->min_key=range->max_key=(char*) ref->key_buff; range->min_length=range->max_length=ref->key_length; @@ -5526,9 +5523,12 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, goto err; } +ok: + my_pthread_setspecific_ptr(THR_MALLOC, old_root); return quick; err: + my_pthread_setspecific_ptr(THR_MALLOC, old_root); delete quick; return 0; } @@ -6481,8 +6481,8 @@ cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, - GA = <G_1, ..., G_k> - from the GROUP BY clause (if any) = SA - if Q is a DISTINCT query (based on the equivalence of DISTINCT and GROUP queries. - - NGA = QA - (GA union C) = {NG_1, ..., NG_m} - the ones not in GROUP BY - and not referenced by MIN/MAX functions. + - NGA = QA - (GA union C) = {NG_1, ..., NG_m} - the ones not in + GROUP BY and not referenced by MIN/MAX functions. with the following properties specified below. SA1. There is at most one attribute in SA referenced by any number of @@ -6860,7 +6860,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree) if (!index_info) /* No usable index found. */ DBUG_RETURN(NULL); - /* Check (SA3,WA1) for the where clause. */ if (!check_group_min_max_predicates(join->conds, min_max_arg_item, (index_info->flags & HA_SPATIAL) ? @@ -7194,16 +7193,16 @@ SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, PARAM *param, group_key_parts [in] Number of index key parts in the group prefix range_tree [in] Tree of ranges for all indexes index_tree [in] The range tree for the current index - quick_prefix_records [in] Number of records retrieved by the internally used - quick range select if any + quick_prefix_records [in] Number of records retrieved by the internally + used quick range select if any have_min [in] True if there is a MIN function have_max [in] True if there is a MAX function read_cost [out] The cost to retrieve rows via this quick select records [out] The number of rows retrieved DESCRIPTION - This method computes the access cost of a TRP_GROUP_MIN_MAX instance and the - number of rows returned. It updates this->read_cost and this->records. + This method computes the access cost of a TRP_GROUP_MIN_MAX instance and + the number of rows returned. It updates this->read_cost and this->records. NOTES The cost computation distinguishes several cases: @@ -7260,8 +7259,8 @@ void cost_group_min_max(TABLE* table, KEY *index_info, uint used_key_parts, double quick_prefix_selectivity; double io_cost; double cpu_cost= 0; /* TODO: CPU cost of index_read calls? */ - DBUG_ENTER("TRP_GROUP_MIN_MAX::cost"); + table_records= table->file->records; keys_per_block= (table->file->block_size / 2 / (index_info->key_length + table->file->ref_length) @@ -7349,7 +7348,6 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows, MEM_ROOT *parent_alloc) { QUICK_GROUP_MIN_MAX_SELECT *quick; - DBUG_ENTER("TRP_GROUP_MIN_MAX::make_quick"); quick= new QUICK_GROUP_MIN_MAX_SELECT(param->table, @@ -7357,7 +7355,8 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows, have_min, have_max, min_max_arg_part, group_prefix_len, used_key_parts, index_info, index, read_cost, records, - key_infix_len, key_infix, parent_alloc); + key_infix_len, key_infix, + parent_alloc); if (!quick) DBUG_RETURN(NULL); @@ -7440,17 +7439,20 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows, None */ -QUICK_GROUP_MIN_MAX_SELECT::QUICK_GROUP_MIN_MAX_SELECT( - TABLE *table, JOIN *join_arg, bool have_min_arg, bool have_max_arg, - KEY_PART_INFO *min_max_arg_part_arg, uint group_prefix_len_arg, - uint used_key_parts_arg, KEY *index_info_arg, uint use_index, - double read_cost_arg, ha_rows records_arg, uint key_infix_len_arg, - byte *key_infix_arg, MEM_ROOT *parent_alloc) -: join(join_arg), index_info(index_info_arg), - group_prefix_len(group_prefix_len_arg), have_min(have_min_arg), - have_max(have_max_arg), seen_first_key(FALSE), - min_max_arg_part(min_max_arg_part_arg), key_infix(key_infix_arg), - key_infix_len(key_infix_len_arg) +QUICK_GROUP_MIN_MAX_SELECT:: +QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join_arg, bool have_min_arg, + bool have_max_arg, + KEY_PART_INFO *min_max_arg_part_arg, + uint group_prefix_len_arg, + uint used_key_parts_arg, KEY *index_info_arg, + uint use_index, double read_cost_arg, + ha_rows records_arg, uint key_infix_len_arg, + byte *key_infix_arg, MEM_ROOT *parent_alloc) + :join(join_arg), index_info(index_info_arg), + group_prefix_len(group_prefix_len_arg), have_min(have_min_arg), + have_max(have_max_arg), seen_first_key(FALSE), + min_max_arg_part(min_max_arg_part_arg), key_infix(key_infix_arg), + key_infix_len(key_infix_len_arg) { head= table; file= head->file; @@ -7463,6 +7465,12 @@ QUICK_GROUP_MIN_MAX_SELECT::QUICK_GROUP_MIN_MAX_SELECT( real_prefix_len= group_prefix_len + key_infix_len; group_prefix= NULL; min_max_arg_len= min_max_arg_part ? min_max_arg_part->store_length : 0; + + /* + We can't have parent_alloc set as the init function can't handle this case + yet. + */ + DBUG_ASSERT(!parent_alloc); if (!parent_alloc) { init_sql_alloc(&alloc, join->thd->variables.range_alloc_block_size, 0); @@ -8571,7 +8579,7 @@ void QUICK_GROUP_MIN_MAX_SELECT::dbug_dump(int indent, bool verbose) } -#endif +#endif /* NOT_USED */ /***************************************************************************** ** Instantiate templates diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index 6b966c28b1a..30033bc39eb 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -186,16 +186,14 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds) if (!ref.key_length) error= table->file->index_first(table->record[0]); else - { error= table->file->index_read(table->record[0],key_buff, ref.key_length, range_fl & NEAR_MIN ? HA_READ_AFTER_KEY : HA_READ_KEY_OR_NEXT); - if (!error && reckey_in_range(0, &ref, item_field->field, - conds, range_fl, prefix_len)) - error= HA_ERR_KEY_NOT_FOUND; - } + if (!error && reckey_in_range(0, &ref, item_field->field, + conds, range_fl, prefix_len)) + error= HA_ERR_KEY_NOT_FOUND; if (table->key_read) { table->key_read= 0; @@ -260,16 +258,14 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds) if (!ref.key_length) error= table->file->index_last(table->record[0]); else - { error= table->file->index_read(table->record[0], key_buff, ref.key_length, range_fl & NEAR_MAX ? HA_READ_BEFORE_KEY : HA_READ_PREFIX_LAST_OR_PREV); - if (!error && reckey_in_range(1, &ref, item_field->field, - conds, range_fl, prefix_len)) - error= HA_ERR_KEY_NOT_FOUND; - } + if (!error && reckey_in_range(1, &ref, item_field->field, + conds, range_fl, prefix_len)) + error= HA_ERR_KEY_NOT_FOUND; if (table->key_read) { table->key_read=0; diff --git a/sql/password.c b/sql/password.c index 115c1ef83ef..e2f7e14d39f 100644 --- a/sql/password.c +++ b/sql/password.c @@ -35,7 +35,7 @@ update user set password=PASSWORD("hello") where user="test" This saves a hashed number as a string in the password field. - The new autentication is performed in following manner: + The new authentication is performed in following manner: SERVER: public_seed=create_random_string() send(public_seed) diff --git a/sql/protocol.cc b/sql/protocol.cc index 187989158df..36296cea59b 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -225,6 +225,10 @@ net_printf(THD *thd, uint errcode, ...) format,args); va_end(args); + /* Replication slave relies on net->last_* to see if there was error */ + net->last_errno= errcode; + strmake(net->last_error, text_pos, sizeof(net->last_error)-1); + #ifndef EMBEDDED_LIBRARY if (net->vio == 0) { @@ -894,10 +898,9 @@ bool Protocol_simple::store_date(TIME *tm) field_types[field_pos] == MYSQL_TYPE_DATE); field_pos++; #endif - char buff[40]; - String tmp((char*) buff,sizeof(buff),&my_charset_bin); - make_date((DATE_TIME_FORMAT *) 0, tm, &tmp); - return net_store_data((char*) tmp.ptr(), tmp.length()); + char buff[MAX_DATE_STRING_REP_LENGTH]; + int length= my_date_to_str(tm, buff); + return net_store_data(buff, (uint) length); } @@ -1108,6 +1111,13 @@ bool Protocol_prep::store_time(TIME *tm) field_pos++; pos= buff+1; pos[0]= tm->neg ? 1 : 0; + if (tm->hour >= 24) + { + /* Fix if we come from Item::send */ + uint days= tm->hour/24; + tm->hour-= days*24; + tm->day+= days; + } int4store(pos+1, tm->day); pos[5]= (uchar) tm->hour; pos[6]= (uchar) tm->minute; diff --git a/sql/records.cc b/sql/records.cc index 8dd4f548093..5963c36afd9 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -156,17 +156,26 @@ void end_read_record(READ_RECORD *info) static int rr_quick(READ_RECORD *info) { - int tmp=info->select->quick->get_next(); - if (tmp) + int tmp; + while ((tmp= info->select->quick->get_next())) { - if (tmp == HA_ERR_END_OF_FILE) - tmp= -1; - else + if (info->thd->killed) { - if (info->print_error) - info->file->print_error(tmp,MYF(0)); - if (tmp < 0) // Fix negative BDB errno - tmp=1; + my_error(ER_SERVER_SHUTDOWN, MYF(0)); + return 1; + } + if (tmp != HA_ERR_RECORD_DELETED) + { + if (tmp == HA_ERR_END_OF_FILE) + tmp= -1; + else + { + if (info->print_error) + info->file->print_error(tmp,MYF(0)); + if (tmp < 0) // Fix negative BDB errno + tmp=1; + } + break; } } return tmp; @@ -330,9 +339,10 @@ static int init_rr_cache(READ_RECORD *info) rec_cache_size=info->cache_records*info->reclength; info->rec_cache_size=info->cache_records*info->ref_length; + // We have to allocate one more byte to use uint3korr (see comments for it) if (info->cache_records <= 2 || !(info->cache=(byte*) my_malloc_lock(rec_cache_size+info->cache_records* - info->struct_length, + info->struct_length+1, MYF(0)))) DBUG_RETURN(1); #ifdef HAVE_purify diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 10ff5fa3596..dc23b014e31 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -14,8 +14,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -// Sasha Pachev <sasha@mysql.com> is currently in charge of this file - #include "mysql_priv.h" #ifdef HAVE_REPLICATION @@ -37,7 +35,7 @@ HASH slave_list; const char *rpl_role_type[] = {"MASTER","SLAVE",NullS}; TYPELIB rpl_role_typelib = {array_elements(rpl_role_type)-1,"", - rpl_role_type}; + rpl_role_type, NULL}; const char* rpl_status_type[]= { @@ -45,7 +43,7 @@ const char* rpl_status_type[]= "RECOVERY_CAPTAIN","NULL",NullS }; TYPELIB rpl_status_typelib= {array_elements(rpl_status_type)-1,"", - rpl_status_type}; + rpl_status_type, NULL}; static Slave_log_event* find_slave_event(IO_CACHE* log, diff --git a/sql/repl_failsafe.h b/sql/repl_failsafe.h index a9c504330ab..ad0219bb735 100644 --- a/sql/repl_failsafe.h +++ b/sql/repl_failsafe.h @@ -1,6 +1,20 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB & Sasha + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #ifdef HAVE_REPLICATION -#ifndef REPL_FAILSAFE_H -#define REPL_FAILSAFE_H #include "mysql.h" #include "my_sys.h" @@ -35,5 +49,4 @@ void end_slave_list(); int register_slave(THD* thd, uchar* packet, uint packet_length); void unregister_slave(THD* thd, bool only_mine, bool need_mutex); -#endif #endif /* HAVE_REPLICATION */ diff --git a/sql/set_var.cc b/sql/set_var.cc index a43a2c13aca..cb8b95f27ec 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -73,13 +73,14 @@ static HASH system_variable_hash; const char *bool_type_names[]= { "OFF", "ON", NullS }; TYPELIB bool_typelib= { - array_elements(bool_type_names)-1, "", bool_type_names + array_elements(bool_type_names)-1, "", bool_type_names, NULL }; const char *delay_key_write_type_names[]= { "OFF", "ON", "ALL", NullS }; TYPELIB delay_key_write_typelib= { - array_elements(delay_key_write_type_names)-1, "", delay_key_write_type_names + array_elements(delay_key_write_type_names)-1, "", + delay_key_write_type_names, NULL }; static int sys_check_charset(THD *thd, set_var *var); @@ -366,6 +367,12 @@ sys_var_thd_ulong sys_net_wait_timeout("wait_timeout", #ifdef HAVE_INNOBASE_DB sys_var_long_ptr sys_innodb_max_dirty_pages_pct("innodb_max_dirty_pages_pct", &srv_max_buf_pool_modified_pct); +sys_var_long_ptr sys_innodb_max_purge_lag("innodb_max_purge_lag", + &srv_max_purge_lag); +sys_var_thd_bool sys_innodb_table_locks("innodb_table_locks", + &SV::innodb_table_locks); +sys_var_long_ptr sys_innodb_autoextend_increment("innodb_autoextend_increment", + &srv_auto_extend_increment); #endif /* Time/date/datetime formats */ @@ -620,6 +627,10 @@ sys_var *sys_variables[]= &sys_os, #ifdef HAVE_INNOBASE_DB &sys_innodb_max_dirty_pages_pct, + &sys_innodb_max_purge_lag, + &sys_innodb_table_locks, + &sys_innodb_max_purge_lag, + &sys_innodb_autoextend_increment, #endif &sys_unique_checks, &sys_updatable_views_with_limit, @@ -696,6 +707,7 @@ struct show_var_st init_vars[]= { {"init_slave", (char*) &sys_init_slave, SHOW_SYS}, #ifdef HAVE_INNOBASE_DB {"innodb_additional_mem_pool_size", (char*) &innobase_additional_mem_pool_size, SHOW_LONG }, + {sys_innodb_autoextend_increment.name, (char*) &sys_innodb_autoextend_increment, SHOW_SYS}, {"innodb_buffer_pool_awe_mem_mb", (char*) &innobase_buffer_pool_awe_mem_mb, SHOW_LONG }, {"innodb_buffer_pool_size", (char*) &innobase_buffer_pool_size, SHOW_LONG }, {"innodb_data_file_path", (char*) &innobase_data_file_path, SHOW_CHAR_PTR}, @@ -715,6 +727,9 @@ struct show_var_st init_vars[]= { {"innodb_log_files_in_group", (char*) &innobase_log_files_in_group, SHOW_LONG}, {"innodb_log_group_home_dir", (char*) &innobase_log_group_home_dir, SHOW_CHAR_PTR}, {sys_innodb_max_dirty_pages_pct.name, (char*) &sys_innodb_max_dirty_pages_pct, SHOW_SYS}, + {sys_innodb_max_purge_lag.name, (char*) &sys_innodb_max_purge_lag, SHOW_SYS}, + {sys_innodb_table_locks.name, (char*) &sys_innodb_table_locks, SHOW_SYS}, + {sys_innodb_max_purge_lag.name, (char*) &sys_innodb_max_purge_lag, SHOW_SYS}, {"innodb_mirrored_log_groups", (char*) &innobase_mirrored_log_groups, SHOW_LONG}, {"innodb_open_files", (char*) &innobase_open_files, SHOW_LONG }, {"innodb_thread_concurrency", (char*) &innobase_thread_concurrency, SHOW_LONG }, @@ -1475,7 +1490,9 @@ bool sys_var::check_set(THD *thd, set_var *var, TYPELIB *enum_names) goto err; var->save_result.ulong_value= ((ulong) find_set(enum_names, res->c_ptr(), - res->length(), &error, &error_len, + res->length(), + NULL, + &error, &error_len, ¬_used)); if (error_len) { @@ -2999,7 +3016,8 @@ void sys_var_thd_table_type::warn_deprecated(THD *thd) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DEPRECATED_SYNTAX, - ER(ER_WARN_DEPRECATED_SYNTAX), "table_type", "storage_engine"); + ER(ER_WARN_DEPRECATED_SYNTAX), "table_type", + "storage_engine"); } void sys_var_thd_table_type::set_default(THD *thd, enum_var_type type) diff --git a/sql/share/charsets/Index.xml b/sql/share/charsets/Index.xml index 5e75cde5f2a..9595d4a7ddb 100644 --- a/sql/share/charsets/Index.xml +++ b/sql/share/charsets/Index.xml @@ -2,6 +2,24 @@ <charsets max-id="94"> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <description> This file lists all of the available character sets. To make maintaining easier please: diff --git a/sql/share/charsets/armscii8.xml b/sql/share/charsets/armscii8.xml index 8ca5675012b..d0ab428345f 100644 --- a/sql/share/charsets/armscii8.xml +++ b/sql/share/charsets/armscii8.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="armscii8"> <ctype> diff --git a/sql/share/charsets/ascii.xml b/sql/share/charsets/ascii.xml index 1007f078f5a..3813bd42601 100644 --- a/sql/share/charsets/ascii.xml +++ b/sql/share/charsets/ascii.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="ascii"> <ctype> diff --git a/sql/share/charsets/cp1250.xml b/sql/share/charsets/cp1250.xml index 5ac63b836b0..8e7102b6737 100644 --- a/sql/share/charsets/cp1250.xml +++ b/sql/share/charsets/cp1250.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp1250"> <ctype> diff --git a/sql/share/charsets/cp1251.xml b/sql/share/charsets/cp1251.xml index 94774cca0f1..7f94788c0d0 100644 --- a/sql/share/charsets/cp1251.xml +++ b/sql/share/charsets/cp1251.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp1251"> <ctype> diff --git a/sql/share/charsets/cp1256.xml b/sql/share/charsets/cp1256.xml index 818a5583368..69eb6a68238 100644 --- a/sql/share/charsets/cp1256.xml +++ b/sql/share/charsets/cp1256.xml @@ -4,6 +4,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp1256"> <ctype> diff --git a/sql/share/charsets/cp1257.xml b/sql/share/charsets/cp1257.xml index bd4489a5a79..93a1bd47a77 100644 --- a/sql/share/charsets/cp1257.xml +++ b/sql/share/charsets/cp1257.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp1257"> <ctype> diff --git a/sql/share/charsets/cp850.xml b/sql/share/charsets/cp850.xml index d750bac37e2..79497aa17f1 100644 --- a/sql/share/charsets/cp850.xml +++ b/sql/share/charsets/cp850.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp850"> <ctype> diff --git a/sql/share/charsets/cp852.xml b/sql/share/charsets/cp852.xml index 958587d0399..73a81e54b02 100644 --- a/sql/share/charsets/cp852.xml +++ b/sql/share/charsets/cp852.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp852"> <ctype> diff --git a/sql/share/charsets/cp866.xml b/sql/share/charsets/cp866.xml index f698e735b58..1a72b396c7c 100644 --- a/sql/share/charsets/cp866.xml +++ b/sql/share/charsets/cp866.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="cp866"> <!-- cp866_DOSCyrillicRussian --> diff --git a/sql/share/charsets/dec8.xml b/sql/share/charsets/dec8.xml index f1bcfd05b30..2cb28cb0f4f 100644 --- a/sql/share/charsets/dec8.xml +++ b/sql/share/charsets/dec8.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="dec8"> <ctype> diff --git a/sql/share/charsets/geostd8.xml b/sql/share/charsets/geostd8.xml index caf01af58d0..c09aa078fb7 100644 --- a/sql/share/charsets/geostd8.xml +++ b/sql/share/charsets/geostd8.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="geostd8"> <ctype> diff --git a/sql/share/charsets/greek.xml b/sql/share/charsets/greek.xml index defeaf049c8..1cfe6b49610 100644 --- a/sql/share/charsets/greek.xml +++ b/sql/share/charsets/greek.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="greek"> <!-- It's ISO Greek rahter than WIN Greek because --> diff --git a/sql/share/charsets/hebrew.xml b/sql/share/charsets/hebrew.xml index ec3fa242ced..5bcf222a728 100644 --- a/sql/share/charsets/hebrew.xml +++ b/sql/share/charsets/hebrew.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="hebrew"> <ctype> diff --git a/sql/share/charsets/hp8.xml b/sql/share/charsets/hp8.xml index 0e16551b1b6..35224f8c544 100644 --- a/sql/share/charsets/hp8.xml +++ b/sql/share/charsets/hp8.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="hp8"> <ctype> diff --git a/sql/share/charsets/keybcs2.xml b/sql/share/charsets/keybcs2.xml index 6180dd8550d..6332891ef23 100644 --- a/sql/share/charsets/keybcs2.xml +++ b/sql/share/charsets/keybcs2.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="keybcs2"> <ctype> diff --git a/sql/share/charsets/koi8r.xml b/sql/share/charsets/koi8r.xml index b8bfc8ecccc..033597e9bfc 100644 --- a/sql/share/charsets/koi8r.xml +++ b/sql/share/charsets/koi8r.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="koi8r"> <ctype> diff --git a/sql/share/charsets/koi8u.xml b/sql/share/charsets/koi8u.xml index 0c05f2f1195..4f5fa35af3d 100644 --- a/sql/share/charsets/koi8u.xml +++ b/sql/share/charsets/koi8u.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="koi8u"> <ctype> diff --git a/sql/share/charsets/latin1.xml b/sql/share/charsets/latin1.xml index 178fd07e7f6..0b6f54dc800 100644 --- a/sql/share/charsets/latin1.xml +++ b/sql/share/charsets/latin1.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="latin1"> <ctype> diff --git a/sql/share/charsets/latin2.xml b/sql/share/charsets/latin2.xml index 4de138d3ea0..702f052604b 100644 --- a/sql/share/charsets/latin2.xml +++ b/sql/share/charsets/latin2.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="latin2"> <ctype> diff --git a/sql/share/charsets/latin5.xml b/sql/share/charsets/latin5.xml index 963691ad110..67e5873c503 100644 --- a/sql/share/charsets/latin5.xml +++ b/sql/share/charsets/latin5.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="latin5"> <ctype> diff --git a/sql/share/charsets/latin7.xml b/sql/share/charsets/latin7.xml index 1c25f5894b6..dd87a1a2d89 100644 --- a/sql/share/charsets/latin7.xml +++ b/sql/share/charsets/latin7.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="latin7"> <ctype> diff --git a/sql/share/charsets/macce.xml b/sql/share/charsets/macce.xml index 7e955157bb7..61f6d79b34f 100644 --- a/sql/share/charsets/macce.xml +++ b/sql/share/charsets/macce.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="macce"> <ctype> diff --git a/sql/share/charsets/macroman.xml b/sql/share/charsets/macroman.xml index ffedaa88274..36c8e8cf13a 100644 --- a/sql/share/charsets/macroman.xml +++ b/sql/share/charsets/macroman.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="macroman"> <ctype> diff --git a/sql/share/charsets/swe7.xml b/sql/share/charsets/swe7.xml index 5bcf9a6501b..2b8ff4edcce 100644 --- a/sql/share/charsets/swe7.xml +++ b/sql/share/charsets/swe7.xml @@ -2,6 +2,24 @@ <charsets> +<copyright> + Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +</copyright> + <charset name="swe7"> <ctype> diff --git a/sql/slave.cc b/sql/slave.cc index 2c6fb06b7cb..88dffbd8411 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1465,6 +1465,7 @@ be equal for replication to work"; Used by fetch_master_table (used by LOAD TABLE tblname FROM MASTER and LOAD DATA FROM MASTER). Drops the table (if 'overwrite' is true) and recreates it from the dump. Honours replication inclusion/exclusion rules. + db must be non-zero (guarded by assertion). RETURN VALUES 0 success @@ -1475,8 +1476,8 @@ static int create_table_from_dump(THD* thd, MYSQL *mysql, const char* db, const char* table_name, bool overwrite) { ulong packet_len; - char *query; - char* save_db; + char *query, *save_db; + uint32 save_db_length; Vio* save_vio; HA_CHECK_OPT check_opt; TABLE_LIST tables; @@ -1532,9 +1533,13 @@ static int create_table_from_dump(THD* thd, MYSQL *mysql, const char* db, thd->proc_info = "Creating table from master dump"; // save old db in case we are creating in a different database save_db = thd->db; + save_db_length= thd->db_length; thd->db = (char*)db; + DBUG_ASSERT(thd->db); + thd->db_length= strlen(thd->db); mysql_parse(thd, thd->query, packet_len); // run create table thd->db = save_db; // leave things the way the were before + thd->db_length= save_db_length; thd->options = save_options; if (thd->query_error) @@ -3468,7 +3473,7 @@ err: IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff)); VOID(pthread_mutex_lock(&LOCK_thread_count)); thd->query = thd->db = 0; // extra safety - thd->query_length = 0; + thd->query_length= thd->db_length= 0; VOID(pthread_mutex_unlock(&LOCK_thread_count)); if (mysql) { @@ -3636,7 +3641,7 @@ Slave SQL thread aborted. Can't execute init_slave query"); while (!sql_slave_killed(thd,rli)) { - thd->proc_info = "Reading event from the relay log"; + thd->proc_info = "Reading event from the relay log"; DBUG_ASSERT(rli->sql_thd == thd); THD_CHECK_SENTRY(thd); if (exec_relay_log_event(thd,rli)) @@ -3646,16 +3651,15 @@ Slave SQL thread aborted. Can't execute init_slave query"); sql_print_error("\ Error running query, slave SQL thread aborted. Fix the problem, and restart \ the slave SQL thread with \"SLAVE START\". We stopped at log \ -'%s' position %s", - RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff)); +'%s' position %s", RPL_LOG_NAME, llstr(rli->group_master_log_pos, llbuff)); goto err; } } /* Thread stopped. Print the current replication position to the log */ - sql_print_information("Slave SQL thread exiting, replication stopped in log \ - '%s' at position %s", - RPL_LOG_NAME, llstr(rli->group_master_log_pos,llbuff)); + sql_print_information("Slave SQL thread exiting, replication stopped in log " + "'%s' at position %s", + RPL_LOG_NAME, llstr(rli->group_master_log_pos,llbuff)); err: VOID(pthread_mutex_lock(&LOCK_thread_count)); @@ -3665,7 +3669,7 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ variables is supposed to set them to 0 before terminating)). */ thd->query= thd->db= thd->catalog= 0; - thd->query_length = 0; + thd->query_length= thd->db_length= 0; VOID(pthread_mutex_unlock(&LOCK_thread_count)); thd->proc_info = "Waiting for slave mutex on exit"; pthread_mutex_lock(&rli->run_lock); diff --git a/sql/slave.h b/sql/slave.h index 46fe58e1976..2a9b96b75a1 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -1,3 +1,19 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #ifdef HAVE_REPLICATION #ifndef SLAVE_H diff --git a/sql/spatial.h b/sql/spatial.h index 45db1ca8d00..b96434831a1 100644 --- a/sql/spatial.h +++ b/sql/spatial.h @@ -97,12 +97,14 @@ struct MBR int equals(const MBR *mbr) { + /* The following should be safe, even if we compare doubles */ return ((mbr->xmin == xmin) && (mbr->ymin == ymin) && (mbr->xmax == xmax) && (mbr->ymax == ymax)); } int disjoint(const MBR *mbr) { + /* The following should be safe, even if we compare doubles */ return ((mbr->xmin > xmax) || (mbr->ymin > ymax) || (mbr->xmax < xmin) || (mbr->ymax < ymin)); } @@ -114,6 +116,7 @@ struct MBR int touches(const MBR *mbr) { + /* The following should be safe, even if we compare doubles */ return ((((mbr->xmin == xmax) || (mbr->xmax == xmin)) && ((mbr->ymin >= ymin) && (mbr->ymin <= ymax) || (mbr->ymax >= ymin) && (mbr->ymax <= ymax))) || @@ -124,18 +127,21 @@ struct MBR int within(const MBR *mbr) { + /* The following should be safe, even if we compare doubles */ return ((mbr->xmin <= xmin) && (mbr->ymin <= ymin) && (mbr->xmax >= xmax) && (mbr->ymax >= ymax)); } int contains(const MBR *mbr) { + /* The following should be safe, even if we compare doubles */ return ((mbr->xmin >= xmin) && (mbr->ymin >= ymin) && (mbr->xmax <= xmax) && (mbr->ymax <= ymax)); } bool inner_point(double x, double y) const { + /* The following should be safe, even if we compare doubles */ return (xmin<x) && (xmax>x) && (ymin<y) && (ymax>x); } @@ -164,6 +170,9 @@ public: return buffer; } + static void operator delete(void *ptr, void *buffer) + {} + enum wkbType { wkb_point= 1, diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index df615383ec3..84c22ee5933 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -203,7 +203,7 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) host.sort= get_sort(2,host.host.hostname,host.db); if (check_no_resolve && hostname_requires_resolving(host.host.hostname)) { - sql_print_error("Warning: 'host' entry '%s|%s' " + sql_print_warning("'host' entry '%s|%s' " "ignored in --skip-name-resolve mode.", host.host.hostname, host.db, host.host.hostname); continue; @@ -271,8 +271,8 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) user.user= get_field(&mem, table->field[1]); if (check_no_resolve && hostname_requires_resolving(user.host.hostname)) { - sql_print_error("Warning: 'user' entry '%s@%s' " - "ignored in --skip-name-resolve mode.", + sql_print_warning("'user' entry '%s@%s' " + "ignored in --skip-name-resolve mode.", user.user, user.host.hostname, user.host.hostname); continue; } @@ -284,16 +284,16 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) { switch (password_len) { case 45: /* 4.1: to be removed */ - sql_print_error("Found 4.1 style password for user '%s@%s'. " - "Ignoring user. " - "You should change password for this user.", - user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); + sql_print_warning("Found 4.1 style password for user '%s@%s'. " + "Ignoring user. " + "You should change password for this user.", + user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); break; default: - sql_print_error("Found invalid password for user: '%s@%s'; " - "Ignoring user", user.user ? user.user : "", - user.host.hostname ? user.host.hostname : ""); + sql_print_warning("Found invalid password for user: '%s@%s'; " + "Ignoring user", user.user ? user.user : "", + user.host.hostname ? user.host.hostname : ""); break; } } @@ -375,15 +375,15 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) db.db=get_field(&mem, table->field[1]); if (!db.db) { - sql_print_error("Found an entry in the 'db' table with empty database name; Skipped"); + sql_print_warning("Found an entry in the 'db' table with empty database name; Skipped"); continue; } db.user=get_field(&mem, table->field[2]); if (check_no_resolve && hostname_requires_resolving(db.host.hostname)) { - sql_print_error("Warning: 'db' entry '%s %s@%s' " - "ignored in --skip-name-resolve mode.", - db.db, db.user, db.host.hostname, db.host.hostname); + sql_print_warning("'db' entry '%s %s@%s' " + "ignored in --skip-name-resolve mode.", + db.db, db.user, db.host.hostname, db.host.hostname); continue; } db.access=get_access(table,3); @@ -740,9 +740,9 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, else { if (global_system_variables.log_warnings) - sql_print_error("X509 ciphers mismatch: should be '%s' but is '%s'", - acl_user->ssl_cipher, - SSL_get_cipher(ssl)); + sql_print_information("X509 ciphers mismatch: should be '%s' but is '%s'", + acl_user->ssl_cipher, + SSL_get_cipher(ssl)); break; } } @@ -764,8 +764,8 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, if (strcmp(acl_user->x509_issuer, ptr)) { if (global_system_variables.log_warnings) - sql_print_error("X509 issuer mismatch: should be '%s' " - "but is '%s'", acl_user->x509_issuer, ptr); + sql_print_information("X509 issuer mismatch: should be '%s' " + "but is '%s'", acl_user->x509_issuer, ptr); free(ptr); break; } @@ -782,7 +782,7 @@ int acl_getroot(THD *thd, USER_RESOURCES *mqh, if (strcmp(acl_user->x509_subject,ptr)) { if (global_system_variables.log_warnings) - sql_print_error("X509 subject mismatch: '%s' vs '%s'", + sql_print_information("X509 subject mismatch: '%s' vs '%s'", acl_user->x509_subject, ptr); } else @@ -2493,7 +2493,8 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (replace_table_table(thd, grant_table, tables[1].table, *Str, db_name, real_name, rights, column_priv, revoke_grant)) - { // Crashend table ?? + { + /* Should only happen if table is crashed */ result= -1; /* purecov: deadcode */ } else if (tables[2].table) @@ -2702,10 +2703,10 @@ my_bool grant_init(THD *org_thd) { if (hostname_requires_resolving(mem_check->host)) { - sql_print_error("Warning: 'tables_priv' entry '%s %s@%s' " - "ignored in --skip-name-resolve mode.", - mem_check->tname, mem_check->user, - mem_check->host, mem_check->host); + sql_print_warning("'tables_priv' entry '%s %s@%s' " + "ignored in --skip-name-resolve mode.", + mem_check->tname, mem_check->user, + mem_check->host, mem_check->host); continue; } } @@ -3710,7 +3711,7 @@ int mysql_drop_user(THD *thd, List <LEX_USER> &list) int mysql_revoke_all(THD *thd, List <LEX_USER> &list) { - uint counter; + uint counter, revoked; int result; ACL_DB *acl_db; TABLE_LIST tables[4]; @@ -3743,73 +3744,96 @@ int mysql_revoke_all(THD *thd, List <LEX_USER> &list) } /* Remove db access privileges */ - for (counter= 0 ; counter < acl_dbs.elements ; counter++) + /* + Because acl_dbs and column_priv_hash shrink and may re-order + as privileges are removed, removal occurs in a repeated loop + until no more privileges are revoked. + */ + do { - const char *user,*host; - - acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); - if (!(user=acl_db->user)) - user= ""; - if (!(host=acl_db->host.hostname)) - host= ""; - - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + for (counter= 0, revoked= 0 ; counter < acl_dbs.elements ; ) { - if (replace_db_table(tables[1].table, acl_db->db, *lex_user, ~0, 1)) - result= -1; + const char *user,*host; + + acl_db=dynamic_element(&acl_dbs,counter,ACL_DB*); + if (!(user=acl_db->user)) + user= ""; + if (!(host=acl_db->host.hostname)) + host= ""; + + if (!strcmp(lex_user->user.str,user) && + !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + { + if (!replace_db_table(tables[1].table, acl_db->db, *lex_user, ~0, 1)) + { + /* + Don't increment counter as replace_db_table deleted the + current element in acl_dbs. + */ + revoked= 1; + continue; + } + result= -1; // Something went wrong + } + counter++; } - } + } while (revoked); /* Remove column access */ - for (counter= 0 ; counter < column_priv_hash.records ; counter++) + do { - const char *user,*host; - GRANT_TABLE *grant_table= (GRANT_TABLE*) hash_element(&column_priv_hash, - counter); - if (!(user=grant_table->user)) - user= ""; - if (!(host=grant_table->host)) - host= ""; - - if (!strcmp(lex_user->user.str,user) && - !my_strcasecmp(system_charset_info, lex_user->host.str, host)) + for (counter= 0, revoked= 0 ; counter < column_priv_hash.records ; ) { - if (replace_table_table(thd,grant_table,tables[2].table,*lex_user, - grant_table->db, - grant_table->tname, - ~0, 0, 1)) - { - result= -1; - continue; - } - if (grant_table->cols) + const char *user,*host; + GRANT_TABLE *grant_table= (GRANT_TABLE*)hash_element(&column_priv_hash, + counter); + if (!(user=grant_table->user)) + user= ""; + if (!(host=grant_table->host)) + host= ""; + + if (!strcmp(lex_user->user.str,user) && + !my_strcasecmp(system_charset_info, lex_user->host.str, host)) { - List<LEX_COLUMN> columns; - if (replace_column_table(grant_table,tables[3].table, *lex_user, - columns, - grant_table->db, - grant_table->tname, - ~0, 1)) + if (replace_table_table(thd,grant_table,tables[2].table,*lex_user, + grant_table->db, + grant_table->tname, + ~0, 0, 1)) + { + result= -1; + } + else + { + if (!grant_table->cols) + { + revoked= 1; + continue; + } + List<LEX_COLUMN> columns; + if (!replace_column_table(grant_table,tables[3].table, *lex_user, + columns, + grant_table->db, + grant_table->tname, + ~0, 1)) + { + revoked= 1; + continue; + } result= -1; + } } + counter++; } - } + } while (revoked); } - + VOID(pthread_mutex_unlock(&acl_cache->lock)); rw_unlock(&LOCK_grant); close_thread_tables(thd); - - /* XXX this should not be necessary. The error message is already printed - by replace_xxx_table. my_error() should be use above instead of - sql_print_error(), and print ER_NONEXISTING_GRANT - as other grant - commands do */ - /* when this code is deleted, the error slot (error 1268) can be reused, - as this error code was not present in any MySQL release */ + if (result) my_error(ER_REVOKE_GRANTS, MYF(0)); - + DBUG_RETURN(result); } diff --git a/sql/sql_analyse.cc b/sql/sql_analyse.cc index 3f75dadb6f0..1e0aebbc1ec 100644 --- a/sql/sql_analyse.cc +++ b/sql/sql_analyse.cc @@ -799,6 +799,13 @@ void field_real::get_opt_type(String *answer, if (min_arg >= 0) answer->append(" UNSIGNED"); } + else if (item->decimals == NOT_FIXED_DEC) + { + if (min_arg >= -FLT_MAX && max_arg <= FLT_MAX) + answer->append("FLOAT", 5); + else + answer->append("DOUBLE", 6); + } else { if (min_arg >= -FLT_MAX && max_arg <= FLT_MAX) diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 4f273fbd0c4..af6c4740e93 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -308,7 +308,7 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, thd->proc_info="Flushing tables"; close_old_data_files(thd,thd->open_tables,1,1); - mysql_ha_close_list(thd, tables); + mysql_ha_flush(thd, tables, MYSQL_HA_REOPEN_ON_USAGE | MYSQL_HA_FLUSH_ALL); bool found=1; /* Wait until all threads has closed all the tables we had locked */ DBUG_PRINT("info", @@ -371,7 +371,7 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) { - bool found_old_table=0; + bool found_old_table; DBUG_ENTER("close_thread_tables"); if (thd->derived_tables && !skip_derived) @@ -406,6 +406,7 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived) DBUG_PRINT("info", ("thd->open_tables: %p", thd->open_tables)); + found_old_table= 0; while (thd->open_tables) found_old_table|=close_thread_table(thd, &thd->open_tables); thd->some_tables_deleted=0; @@ -876,8 +877,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { for (table= thd->temporary_tables; table ; table=table->next) { - if (table->key_length == key_length + 8 && - !memcmp(table->table_cache_key, key, key_length + 8)) + if (table->key_length == key_length + TMP_TABLE_KEY_EXTRA && + !memcmp(table->table_cache_key, key, + key_length + TMP_TABLE_KEY_EXTRA)) { if (table->query_id == thd->query_id) { @@ -888,7 +890,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->query_id= thd->query_id; table->clear_query_id= 1; thd->tmp_table_used= 1; - goto reset; + DBUG_PRINT("info",("Using temporary table")); + goto reset; } } } @@ -903,6 +906,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->query_id != thd->query_id) { table->query_id=thd->query_id; + DBUG_PRINT("info",("Using locked table")); goto reset; } } @@ -954,7 +958,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } /* close handler tables which are marked for flush */ - mysql_ha_close_list(thd, (TABLE_LIST*) NULL, /*flushed*/ 1); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); for (table=(TABLE*) hash_search(&open_cache,(byte*) key,key_length) ; table && table->in_use ; @@ -1047,6 +1051,31 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, for (uint i=0 ; i < table->fields ; i++) table->field[i]->table_name=table->table_name; } +#if MYSQL_VERSION_ID < 40100 + /* + If per-connection "new" variable (represented by variables.new_mode) + is set then we should pretend that the length of TIMESTAMP field is 19. + The cheapest (from perfomance viewpoint) way to achieve that is to set + field_length of all Field_timestamp objects in a table after opening + it (to 19 if new_mode is true or to original field length otherwise). + We save value of new_mode variable in TABLE::timestamp_mode to + not perform this setup if new_mode value is the same between sequential + table opens. + */ + my_bool new_mode= thd->variables.new_mode; + if (table->timestamp_mode != new_mode) + { + for (uint i=0 ; i < table->fields ; i++) + { + Field *field= table->field[i]; + + if (field->type() == FIELD_TYPE_TIMESTAMP) + field->field_length= new_mode ? 19 : + ((Field_timestamp *)(field))->orig_field_length; + } + table->timestamp_mode= new_mode; + } +#endif /* These variables are also set in reopen_table() */ table->tablenr=thd->current_tablenr++; table->used_fields=0; @@ -1056,7 +1085,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->keys_in_use_for_query= table->keys_in_use; table->used_keys= table->keys_for_keyread; if (table->timestamp_field) - table->timestamp_field->set_timestamp_offsets(); + table->timestamp_field_type= table->timestamp_field->get_auto_set_type(); table_list->updatable= 1; // It is not derived table nor non-updatable VIEW DBUG_ASSERT(table->key_read == 0); DBUG_RETURN(table); @@ -1339,7 +1368,7 @@ bool wait_for_tables(THD *thd) { thd->some_tables_deleted=0; close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); - mysql_ha_close_list(thd, (TABLE_LIST*) NULL, /*flushed*/ 1); + mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE); if (!table_is_used(thd->open_tables,1)) break; (void) pthread_cond_wait(&COND_refresh,&LOCK_open); @@ -1469,7 +1498,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, */ if (discover_retry_count++ != 0) goto err; - if (create_table_from_handler(db, name, true) != 0) + if (ha_create_table_from_engine(thd, db, name, TRUE) != 0) goto err; thd->clear_error(); // Clear error message @@ -1510,7 +1539,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, /* Give right error message */ thd->clear_error(); my_error(ER_NOT_KEYFILE, MYF(0), name, my_errno); - sql_print_error("Error: Couldn't repair table: %s.%s",db,name); + sql_print_error("Couldn't repair table: %s.%s",db,name); if (entry->file) closefrm(entry); error=1; @@ -1557,7 +1586,7 @@ static int open_unireg_entry(THD *thd, TABLE *entry, const char *db, DBA on top of warning the client (which will automatically be done because of MYF(MY_WME) in my_malloc() above). */ - sql_print_error("Error: when opening HEAP table, could not allocate \ + sql_print_error("When opening HEAP table, could not allocate \ memory to write 'DELETE FROM `%s`.`%s`' to the binary log",db,name); delete entry->triggers; if (entry->file) @@ -1827,13 +1856,22 @@ int open_and_lock_tables(THD *thd, TABLE_LIST *tables) { DBUG_ENTER("open_and_lock_tables"); uint counter; - if (open_tables(thd, tables, &counter) || lock_tables(thd, tables, counter) - || mysql_handle_derived(thd->lex)) + if (open_tables(thd, tables, &counter) || + lock_tables(thd, tables, counter) || + mysql_handle_derived(thd->lex)) DBUG_RETURN(thd->net.report_error ? -1 : 1); /* purecov: inspected */ - /* - Let us propagate pointers to open tables from global table list - to table lists in particular selects if needed. - */ + relink_tables_for_derived(thd); + DBUG_RETURN(0); +} + + +/* + Let us propagate pointers to open tables from global table list + to table lists in particular selects if needed. +*/ + +void relink_tables_for_derived(THD *thd) +{ if (thd->lex->all_selects_list->next_select_in_list() || thd->lex->time_zone_tables_used) { @@ -1850,7 +1888,6 @@ int open_and_lock_tables(THD *thd, TABLE_LIST *tables) } } } - DBUG_RETURN(0); } @@ -1890,7 +1927,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count) if (!table->placeholder()) *(ptr++)= table->table; } - if (!(thd->lock=mysql_lock_tables(thd,start,count))) + if (!(thd->lock=mysql_lock_tables(thd,start, (uint) (ptr - start)))) return -1; /* purecov: inspected */ } else @@ -1983,8 +2020,8 @@ bool rm_temporary_table(enum db_type base, char *path) if (file && file->delete_table(path)) { error=1; - sql_print_error("Warning: Could not remove tmp table: '%s', error: %d", - path, my_errno); + sql_print_warning("Could not remove tmp table: '%s', error: %d", + path, my_errno); } delete file; DBUG_RETURN(error); @@ -2367,14 +2404,17 @@ find_field_in_tables(THD *thd, Item_ident *item, TABLE_LIST *tables, return not_found_item, report other errors, return 0 IGNORE_ERRORS Do not report errors, return 0 if error - + unaliased Set to true if item is field which was found + by original field name and not by its alias + in item list. Set to false otherwise. + RETURN VALUES 0 Item is not found or item is not unique, error message is reported not_found_item Function was called with report_error == REPORT_EXCEPT_NOT_FOUND and item was not found. No error message was reported - found field + found field */ // Special Item pointer for find_item_in_list returning @@ -2383,7 +2423,7 @@ const Item **not_found_item= (const Item**) 0x1; Item ** find_item_in_list(Item *find, List<Item> &items, uint *counter, - find_item_error_report_type report_error) + find_item_error_report_type report_error, bool *unaliased) { List_iterator<Item> li(items); Item **found=0, **found_unaliased= 0, *item; @@ -2392,6 +2432,9 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, const char *table_name=0; bool found_unaliased_non_uniq= 0; uint unaliased_counter; + + *unaliased= FALSE; + if (find->type() == Item::FIELD_ITEM || find->type() == Item::REF_ITEM) { field_name= ((Item_ident*) find)->field_name; @@ -2419,32 +2462,42 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, /* If table name is specified we should find field 'field_name' in table 'table_name'. According to SQL-standard we should ignore - aliases in this case. Note that we should prefer fields from the - select list over other fields from the tables participating in - this select in case of ambiguity. + aliases in this case. + + Since we should NOT prefer fields from the select list over + other fields from the tables participating in this select in + case of ambiguity we have to do extra check outside this function. We use strcmp for table names and database names as these may be - case sensitive. - In cases where they are not case sensitive, they are always in lower - case. + case sensitive. In cases where they are not case sensitive, they + are always in lower case. + + item_field->field_name and item_field->table_name can be 0x0 if + item is not fix_field()'ed yet. */ - if (!my_strcasecmp(system_charset_info, item_field->field_name, + if (item_field->field_name && item_field->table_name && + !my_strcasecmp(system_charset_info, item_field->field_name, field_name) && !strcmp(item_field->table_name, table_name) && (!db_name || (item_field->db_name && !strcmp(item_field->db_name, db_name)))) { - if (found) + if (found_unaliased) { - if ((*found)->eq(item, 0)) - continue; // Same field twice + if ((*found_unaliased)->eq(item, 0)) + continue; + /* + Two matching fields in select list. + We already can bail out because we are searching through + unaliased names only and will have duplicate error anyway. + */ if (report_error != IGNORE_ERRORS) my_printf_error(ER_NON_UNIQ_ERROR, ER(ER_NON_UNIQ_ERROR), MYF(0), find->full_name(), current_thd->where); return (Item**) 0; } - found= li.ref(); - *counter= i; + found_unaliased= li.ref(); + unaliased_counter= i; if (db_name) break; // Perfect match } @@ -2516,6 +2569,7 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, { found= found_unaliased; *counter= unaliased_counter; + *unaliased= TRUE; } } if (found) @@ -2540,8 +2594,11 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, uint wild_num) { Item *item; + DBUG_ENTER("setup_wild"); + if (!wild_num) - return 0; + DBUG_RETURN(0); + Item_arena *arena= thd->current_arena, backup; /* @@ -2556,7 +2613,8 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, List_iterator<Item> it(fields); while (wild_num && (item= it++)) { - if (item->type() == Item::FIELD_ITEM && ((Item_field*) item)->field_name && + if (item->type() == Item::FIELD_ITEM && + ((Item_field*) item)->field_name && ((Item_field*) item)->field_name[0] == '*' && !((Item_field*) item)->field) { @@ -2579,7 +2637,7 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, { if (arena) thd->restore_backup_item_arena(arena, &backup); - return (-1); + DBUG_RETURN(-1); } if (sum_func_list) { @@ -2602,7 +2660,7 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, thd->restore_backup_item_arena(arena, &backup); } - return 0; + DBUG_RETURN(0); } /**************************************************************************** @@ -2635,7 +2693,7 @@ int setup_fields(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, *(ref++)= item; if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM && sum_func_list) - item->split_sum_func(ref_pointer_array, *sum_func_list); + item->split_sum_func(thd, ref_pointer_array, *sum_func_list); thd->used_tables|=item->used_tables(); } DBUG_RETURN(test(thd->net.report_error)); @@ -2647,23 +2705,21 @@ int setup_fields(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, SYNOPSIS setup_tables() - thd - thread handler - tables - tables list - conds - condition of current SELECT (can be changed by VIEW) + thd Thread handler + tables Table list + conds Condition of current SELECT (can be changed by VIEW) - RETURN - 0 ok; In this case *map will includes the choosed index - 1 error - - NOTE - Remap table numbers if INSERT ... SELECT - Check also that the 'used keys' and 'ignored keys' exists and set up the - table structure accordingly + NOTE + Remap table numbers if INSERT ... SELECT + Check also that the 'used keys' and 'ignored keys' exists and set up the + table structure accordingly - This has to be called for all tables that are used by items, as otherwise - table->map is not set and all Item_field will be regarded as const items. + This has to be called for all tables that are used by items, as otherwise + table->map is not set and all Item_field will be regarded as const items. - if tables do not contain VIEWs it is OK to pass 0 as conds + RETURN + 0 ok; In this case *map will includes the choosed index + 1 error */ bool setup_tables(THD *thd, TABLE_LIST *tables, Item **conds) @@ -2973,8 +3029,7 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds) { table_map not_null_tables= 0; SELECT_LEX *select_lex= thd->lex->current_select; - Item_arena *arena= thd->current_arena; - Item_arena backup; + Item_arena *arena= thd->current_arena, backup; bool save_wrapper= thd->lex->current_select->no_wrap_view_item; DBUG_ENTER("setup_conds"); @@ -3090,7 +3145,7 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds) { if (t2_field != view_ref_found) { - if (!(item_t2= new Item_field(t2_field))) + if (!(item_t2= new Item_field(thd, t2_field))) goto err; /* Mark field used for table cache */ t2_field->query_id= thd->query_id; @@ -3143,6 +3198,8 @@ int setup_conds(THD *thd,TABLE_LIST *tables,COND **conds) } } } + else if (arena) + thd->restore_backup_item_arena(arena, &backup); } embedding= embedded->embedding; } @@ -3301,8 +3358,15 @@ void flush_tables() /* -** Mark all entries with the table as deleted to force an reopen of the table -** Returns true if the table is in use by another thread + Mark all entries with the table as deleted to force an reopen of the table + + The table will be closed (not stored in cache) by the current thread when + close_thread_tables() is called. + + RETURN + 0 This thread now have exclusive access to this table and no other thread + can access the table until close_thread_tables() is called. + 1 Table is in use by another thread */ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 35bf6f8701f..39ee8f61f1a 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -363,7 +363,7 @@ TODO list: const char *query_cache_type_names[]= { "OFF", "ON", "DEMAND",NullS }; TYPELIB query_cache_type_typelib= { - array_elements(query_cache_type_names)-1,"", query_cache_type_names + array_elements(query_cache_type_names)-1,"", query_cache_type_names, NULL }; /***************************************************************************** @@ -788,6 +788,9 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) thd->variables.collation_connection->number; flags.limit= thd->variables.select_limit; flags.time_zone= thd->variables.time_zone; + flags.sql_mode= thd->variables.sql_mode; + flags.max_sort_length= thd->variables.max_sort_length; + flags.group_concat_max_len= thd->variables.group_concat_max_len; STRUCT_LOCK(&structure_guard_mutex); if (query_cache_size == 0) @@ -980,8 +983,11 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) flags.collation_connection_num= thd->variables.collation_connection->number; flags.limit= thd->variables.select_limit; flags.time_zone= thd->variables.time_zone; + flags.sql_mode= thd->variables.sql_mode; + flags.max_sort_length= thd->variables.max_sort_length; + flags.group_concat_max_len= thd->variables.group_concat_max_len; memcpy((void *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)), - &flags, QUERY_CACHE_FLAGS_SIZE); + &flags, QUERY_CACHE_FLAGS_SIZE); query_block = (Query_cache_block *) hash_search(&queries, (byte*) sql, tot_length); /* Quick abort on unlocked data */ @@ -1025,9 +1031,38 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) for (; block_table != block_table_end; block_table++) { TABLE_LIST table_list; - bzero((char*) &table_list,sizeof(table_list)); + TABLE *tmptable; Query_cache_table *table = block_table->parent; + + /* + Check that we have not temporary tables with same names of tables + of this query. If we have such tables, we will not send data from + query cache, because temporary tables hide real tables by which + query in query cache was made. + */ + for (tmptable= thd->temporary_tables; tmptable ; tmptable= tmptable->next) + { + if (tmptable->key_length - TMP_TABLE_KEY_EXTRA == table->key_length() && + !memcmp(tmptable->table_cache_key, table->data(), + table->key_length())) + { + DBUG_PRINT("qcache", + ("Temporary table detected: '%s.%s'", + table_list.db, table_list.alias)); + STRUCT_UNLOCK(&structure_guard_mutex); + /* + We should not store result of this query because it contain + temporary tables => assign following variable to make check + faster. + */ + thd->lex->safe_to_cache_query=0; + BLOCK_UNLOCK_RD(query_block); + DBUG_RETURN(-1); + } + } + + bzero((char*) &table_list,sizeof(table_list)); table_list.db = table->db(); table_list.alias= table_list.real_name= table->table(); #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -1533,11 +1568,11 @@ ulong Query_cache::init_cache() query_cache_table_get_key, 0, 0)); #else /* - On windows, OS/2, MacOS X with HFS+ or any other case insensitive - file system if lower_case_table_names!=0 we have same situation as - in previous case, but if lower_case_table_names==0 then we should - not distinguish cases (to be compatible in behavior with underlaying - file system) and so should use case insensitive collation for + On windows, OS/2, MacOS X with HFS+ or any other case insensitive + file system if lower_case_table_names!=0 we have same situation as + in previous case, but if lower_case_table_names==0 then we should + not distinguish cases (to be compatible in behavior with underlying + file system) and so should use case insensitive collation for comparison. */ VOID(hash_init(&tables, @@ -1946,7 +1981,6 @@ inline ulong Query_cache::get_min_append_result_data_size() /* Allocate one or more blocks to hold data */ - my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block, ulong data_len, Query_cache_block *query_block, @@ -1954,55 +1988,55 @@ my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block, { ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) + ALIGN_SIZE(sizeof(Query_cache_result))); - ulong len= data_len + all_headers_len; - ulong align_len= ALIGN_SIZE(len); - DBUG_ENTER("Query_cache::allocate_data_chain"); - DBUG_PRINT("qcache", ("data_len %lu, all_headers_len %lu", - data_len, all_headers_len)); - ulong min_size = (first_block_arg ? get_min_first_result_data_size(): get_min_append_result_data_size()); - *result_block = allocate_block(max(min_size, align_len), - min_result_data_size == 0, - all_headers_len + min_result_data_size, - 1); - my_bool success = (*result_block != 0); - if (success) + Query_cache_block *prev_block= NULL; + Query_cache_block *new_block; + DBUG_ENTER("Query_cache::allocate_data_chain"); + DBUG_PRINT("qcache", ("data_len %lu, all_headers_len %lu", + data_len, all_headers_len)); + + do { - Query_cache_block *new_block= *result_block; + ulong len= data_len + all_headers_len; + ulong align_len= ALIGN_SIZE(len); + + if (!(new_block= allocate_block(max(min_size, align_len), + min_result_data_size == 0, + all_headers_len + min_result_data_size, + 1))) + { + DBUG_PRINT("warning", ("Can't allocate block for results")); + DBUG_RETURN(FALSE); + } + new_block->n_tables = 0; - new_block->used = 0; + new_block->used = min(len, new_block->length); new_block->type = Query_cache_block::RES_INCOMPLETE; new_block->next = new_block->prev = new_block; Query_cache_result *header = new_block->result(); header->parent(query_block); - if (new_block->length < len) - { - /* - We got less memory then we need (no big memory blocks) => - Continue to allocated more blocks until we got everything we need. - */ - Query_cache_block *next_block; - if ((success = allocate_data_chain(&next_block, - len - new_block->length, - query_block, first_block_arg))) - double_linked_list_join(new_block, next_block); - } - if (success) - { - new_block->used = min(len, new_block->length); - - DBUG_PRINT("qcache", ("Block len %lu used %lu", + DBUG_PRINT("qcache", ("Block len %lu used %lu", new_block->length, new_block->used)); - } + + if (prev_block) + double_linked_list_join(prev_block, new_block); else - DBUG_PRINT("warning", ("Can't allocate block for continue")); - } - else - DBUG_PRINT("warning", ("Can't allocate block for results")); - DBUG_RETURN(success); + *result_block= new_block; + if (new_block->length >= len) + break; + + /* + We got less memory then we need (no big memory blocks) => + Continue to allocated more blocks until we got everything we need. + */ + data_len= len - new_block->length; + prev_block= new_block; + } while(1); + + DBUG_RETURN(TRUE); } /***************************************************************************** diff --git a/sql/sql_cache.h b/sql/sql_cache.h index b54bc4f7b7b..93d89aeae4f 100644 --- a/sql/sql_cache.h +++ b/sql/sql_cache.h @@ -53,10 +53,6 @@ of list of free blocks */ #define QUERY_CACHE_MEM_BIN_TRY 5 -/* query flags masks */ -#define QUERY_CACHE_CLIENT_LONG_FLAG_MASK 0x80 -#define QUERY_CACHE_CHARSET_CONVERT_MASK 0x7F - /* packing parameters */ #define QUERY_CACHE_PACK_ITERATION 2 #define QUERY_CACHE_PACK_LIMIT (512*1024L) @@ -148,14 +144,14 @@ struct Query_cache_query struct Query_cache_table { char *tbl; - uint key_len; + uint32 key_len; uint8 table_type; inline char *db() { return (char *) data(); } inline char *table() { return tbl; } inline void table(char *table) { tbl= table; } - inline uint key_length() { return key_len; } - inline void key_length(uint len) { key_len= len; } + inline uint32 key_length() { return key_len; } + inline void key_length(uint32 len) { key_len= len; } inline uint8 type() { return table_type; } inline void type(uint8 t) { table_type= t; } inline gptr data() diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 392534492df..9bf7cff85c0 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -176,6 +176,7 @@ THD::THD() query_error= tmp_table_used= 0; next_insert_id=last_insert_id=0; open_tables= temporary_tables= handler_tables= derived_tables= 0; + hash_clear(&handler_tables_hash); tmp_table=0; lock=locked_tables=0; used_tables=0; @@ -225,8 +226,7 @@ THD::THD() init(); /* Initialize sub structures */ - clear_alloc_root(&transaction.mem_root); - init_alloc_root(&warn_root, WARN_ALLOC_BLOCK_SIZE, WARN_ALLOC_PREALLOC_SIZE); + init_sql_alloc(&warn_root, WARN_ALLOC_BLOCK_SIZE, WARN_ALLOC_PREALLOC_SIZE); user_connect=(USER_CONN *)0; hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0, (hash_get_key) get_var_key, @@ -265,6 +265,7 @@ THD::THD() transaction.trans_log.end_of_file= max_binlog_cache_size; } #endif + init_sql_alloc(&transaction.mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); { ulong tmp=sql_rnd_with_mutex(); randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::query_id); @@ -310,12 +311,13 @@ void THD::init(void) void THD::init_for_queries() { - init_sql_alloc(&mem_root, - variables.query_alloc_block_size, - variables.query_prealloc_size); - init_sql_alloc(&transaction.mem_root, - variables.trans_alloc_block_size, - variables.trans_prealloc_size); + ha_enable_transaction(this,TRUE); + + reset_root_defaults(&mem_root, variables.query_alloc_block_size, + variables.query_prealloc_size); + reset_root_defaults(&transaction.mem_root, + variables.trans_alloc_block_size, + variables.trans_prealloc_size); } @@ -335,6 +337,7 @@ void THD::change_user(void) cleanup(); cleanup_done= 0; init(); + stmt_map.reset(); hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0, (hash_get_key) get_var_key, (hash_free_key) free_user_var, 0); @@ -354,11 +357,9 @@ void THD::cleanup(void) lock=locked_tables; locked_tables=0; close_thread_tables(this); } - if (handler_tables) - { - open_tables=handler_tables; handler_tables=0; - close_thread_tables(this); - } + mysql_ha_flush(this, (TABLE_LIST*) 0, + MYSQL_HA_CLOSE_FINAL | MYSQL_HA_FLUSH_ALL); + hash_free(&handler_tables_hash); close_temporary_tables(this); my_free((char*) variables.time_format, MYF(MY_ALLOW_ZERO_PTR)); my_free((char*) variables.date_format, MYF(MY_ALLOW_ZERO_PTR)); @@ -709,27 +710,29 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) return new_table; } + int THD::send_explain_fields(select_result *result) { List<Item> field_list; Item *item; + CHARSET_INFO *cs= system_charset_info; field_list.push_back(new Item_return_int("id",3, MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("select_type",19)); - field_list.push_back(new Item_empty_string("table",NAME_LEN)); - field_list.push_back(new Item_empty_string("type",10)); + field_list.push_back(new Item_empty_string("select_type", 19, cs)); + field_list.push_back(new Item_empty_string("table", NAME_LEN, cs)); + field_list.push_back(new Item_empty_string("type", 10, cs)); field_list.push_back(item=new Item_empty_string("possible_keys", - NAME_LEN*MAX_KEY)); + NAME_LEN*MAX_KEY, cs)); item->maybe_null=1; - field_list.push_back(item=new Item_empty_string("key",NAME_LEN)); + field_list.push_back(item=new Item_empty_string("key", NAME_LEN, cs)); item->maybe_null=1; field_list.push_back(item=new Item_empty_string("key_len", NAME_LEN*MAX_KEY)); item->maybe_null=1; field_list.push_back(item=new Item_empty_string("ref", - NAME_LEN*MAX_REF_PARTS)); + NAME_LEN*MAX_REF_PARTS, cs)); item->maybe_null=1; - field_list.push_back(new Item_return_int("rows",10, MYSQL_TYPE_LONGLONG)); - field_list.push_back(new Item_empty_string("Extra",255)); + field_list.push_back(new Item_return_int("rows", 10, MYSQL_TYPE_LONGLONG)); + field_list.push_back(new Item_empty_string("Extra", 255, cs)); return (result->send_fields(field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); } @@ -751,6 +754,54 @@ void THD::close_active_vio() #endif +struct Item_change_record: public ilink +{ + Item **place; + Item *old_value; + /* Placement new was hidden by `new' in ilink (TODO: check): */ + static void *operator new(size_t size, void *mem) { return mem; } +}; + + +/* + Register an item tree tree transformation, performed by the query + optimizer. We need a pointer to runtime_memroot because it may be != + thd->mem_root (due to possible set_n_backup_item_arena called for thd). +*/ + +void THD::nocheck_register_item_tree_change(Item **place, Item *old_value, + MEM_ROOT *runtime_memroot) +{ + Item_change_record *change; + /* + Now we use one node per change, which adds some memory overhead, + but still is rather fast as we use alloc_root for allocations. + A list of item tree changes of an average query should be short. + */ + void *change_mem= alloc_root(runtime_memroot, sizeof(*change)); + if (change_mem == 0) + { + fatal_error(); + return; + } + change= new (change_mem) Item_change_record; + change->place= place; + change->old_value= old_value; + change_list.append(change); +} + + +void THD::rollback_item_tree_changes() +{ + I_List_iterator<Item_change_record> it(change_list); + Item_change_record *change; + while ((change= it++)) + *change->place= change->old_value; + /* We can forget about changes memory: it's allocated in runtime memroot */ + change_list.empty(); +} + + /***************************************************************************** ** Functions to provide a interface to select results *****************************************************************************/ @@ -847,7 +898,8 @@ bool select_send::send_eof() /* Unlock tables before sending packet to gain some speed */ if (thd->lock) { - mysql_unlock_tables(thd, thd->lock); thd->lock=0; + mysql_unlock_tables(thd, thd->lock); + thd->lock=0; } if (!thd->net.report_error) { @@ -948,9 +1000,14 @@ static File create_file(THD *thd, char *path, sql_exchange *exchange, option|= MY_REPLACE_DIR; // Force use of db directory #endif - strxnmov(path, FN_REFLEN, mysql_real_data_home, thd->db ? thd->db : "", - NullS); - (void) fn_format(path, exchange->file_name, path, "", option); + if (!dirname_length(exchange->file_name)) + { + strxnmov(path, FN_REFLEN, mysql_real_data_home, thd->db ? thd->db : "", NullS); + (void) fn_format(path, exchange->file_name, path, "", option); + } + else + (void) fn_format(path, exchange->file_name, mysql_real_data_home, "", option); + if (!access(path, F_OK)) { my_error(ER_FILE_EXISTS_ERROR, MYF(0), exchange->file_name); @@ -1388,6 +1445,17 @@ void select_dumpvar::cleanup() } +/* + Create arena for already constructed THD. + + SYNOPSYS + Item_arena() + thd - thread for which arena is created + + DESCRIPTION + Create arena for already existing THD using its variables as parameters + for memory root initialization. +*/ Item_arena::Item_arena(THD* thd) :free_list(0), state(INITIALIZED) @@ -1398,24 +1466,31 @@ Item_arena::Item_arena(THD* thd) } -/* This constructor is called when Item_arena is a subobject of THD */ +/* + Create arena and optionally initialize memory root. -Item_arena::Item_arena() - :free_list(0), - state(CONVENTIONAL_EXECUTION) -{ - clear_alloc_root(&mem_root); -} + SYNOPSYS + Item_arena() + init_mem_root - whenever we need to initialize memory root + DESCRIPTION + Create arena and optionally initialize memory root with minimal + possible parameters. + NOTE + We use this constructor when arena is part of THD, but reinitialize + its memory root in THD::init_for_queries() before execution of real + statements. +*/ Item_arena::Item_arena(bool init_mem_root) :free_list(0), - state(INITIALIZED) + state(CONVENTIONAL_EXECUTION) { if (init_mem_root) - clear_alloc_root(&mem_root); + init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } + Item_arena::Type Item_arena::type() const { DBUG_ASSERT(0); /* Should never be called */ @@ -1423,10 +1498,6 @@ Item_arena::Type Item_arena::type() const } -Item_arena::~Item_arena() -{} - - /* Statement functions */ @@ -1451,7 +1522,7 @@ Statement::Statement(THD *thd) */ Statement::Statement() - :Item_arena(), + :Item_arena((bool)TRUE), id(0), set_query_id(1), allow_sum_func(0), /* initialized later */ @@ -1496,7 +1567,7 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup) } -void Statement::end_statement() +void THD::end_statement() { /* Cleanup SQL processing state to resuse this statement in next query. */ lex_end(lex); @@ -1524,8 +1595,16 @@ void Item_arena::restore_backup_item_arena(Item_arena *set, Item_arena *backup) { set->set_item_arena(this); set_item_arena(backup); - // reset backup mem_root to avoid its freeing +#ifdef NOT_NEEDED_NOW + /* + Reset backup mem_root to avoid its freeing. + Since Item_arena's mem_root is freed only when it is part of Statement + we need this only if we use some Statement's arena as backup storage. + But we do this only with THD::stmt_backup and this Statement is specially + handled in this respect. So this code is not really needed now. + */ clear_alloc_root(&backup->mem_root); +#endif } void Item_arena::set_item_arena(Item_arena *set) @@ -1573,7 +1652,7 @@ Statement_map::Statement_map() : START_STMT_HASH_SIZE = 16, START_NAME_HASH_SIZE = 16 }; - hash_init(&st_hash, default_charset_info, START_STMT_HASH_SIZE, 0, 0, + hash_init(&st_hash, &my_charset_bin, START_STMT_HASH_SIZE, 0, 0, get_statement_id_as_hash_key, delete_statement_as_hash_key, MYF(0)); hash_init(&names_hash, system_charset_info, START_NAME_HASH_SIZE, 0, 0, diff --git a/sql/sql_class.h b/sql/sql_class.h index 32f156fbee8..6e0aacc4fce 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -427,6 +427,9 @@ struct system_variables my_bool low_priority_updates; my_bool new_mode; my_bool query_cache_wlock_invalidate; +#ifdef HAVE_INNOBASE_DB + my_bool innodb_table_locks; +#endif /* HAVE_INNOBASE_DB */ my_bool old_passwords; /* Only charset part of these variables is sensible */ @@ -527,15 +530,29 @@ public: STATEMENT, PREPARED_STATEMENT, STORED_PROCEDURE }; + /* + This constructor is used only when Item_arena is created as + backup storage for another instance of Item_arena. + */ + Item_arena() {}; + /* + Create arena for already constructed THD using its variables as + parameters for memory root initialization. + */ Item_arena(THD *thd); - Item_arena(); + /* + Create arena and optionally init memory root with minimal values. + Particularly used if Item_arena is part of Statement. + */ Item_arena(bool init_mem_root); virtual Type type() const; - virtual ~Item_arena(); + virtual ~Item_arena() {}; inline bool is_stmt_prepare() const { return (int)state < (int)PREPARED; } inline bool is_first_stmt_execute() const { return state == PREPARED; } - inline bool is_conventional() const + inline bool is_stmt_execute() const + { return state == PREPARED || state == EXECUTED; } + inline bool is_conventional_execution() const { return state == CONVENTIONAL_EXECUTION; } inline gptr alloc(unsigned int size) { return alloc_root(&mem_root,size); } inline gptr calloc(unsigned int size) @@ -620,6 +637,23 @@ public: Points to the query associated with this statement. It's const, but we need to declare it char * because all table handlers are written in C and need to point to it. + + Note that (A) if we set query = NULL, we must at the same time set + query_length = 0, and protect the whole operation with the + LOCK_thread_count mutex. And (B) we are ONLY allowed to set query to a + non-NULL value if its previous value is NULL. We do not need to protect + operation (B) with any mutex. To avoid crashes in races, if we do not + know that thd->query cannot change at the moment, one should print + thd->query like this: + (1) reserve the LOCK_thread_count mutex; + (2) check if thd->query is NULL; + (3) if not NULL, then print at most thd->query_length characters from + it. We will see the query_length field as either 0, or the right value + for it. + Assuming that the write and read of an n-bit memory field in an n-bit + computer is atomic, we can avoid races in the above way. + This printing is needed at least in SHOW PROCESSLIST and SHOW INNODB + STATUS. */ char *query; uint32 query_length; // current query length @@ -642,12 +676,6 @@ public: void restore_backup_statement(Statement *stmt, Statement *backup); /* return class type */ virtual Type type() const; - - /* - Cleanup statement parse state (parse tree, lex) after execution of - a non-prepared SQL statement. - */ - void end_statement(); }; @@ -657,7 +685,7 @@ public: assignment in Statement::Statement) Non-empty statement names are unique too: attempt to insert a new statement with duplicate name causes older statement to be deleted - + Statements are auto-deleted when they are removed from the map and when the map is deleted. */ @@ -666,7 +694,7 @@ class Statement_map { public: Statement_map(); - + int insert(Statement *statement); Statement *find_by_name(LEX_STRING *name) @@ -699,11 +727,18 @@ public: } hash_delete(&st_hash, (byte *) statement); } + /* Erase all statements (calls Statement destructor) */ + void reset() + { + hash_reset(&names_hash); + hash_reset(&st_hash); + last_found_statement= 0; + } ~Statement_map() { - hash_free(&st_hash); hash_free(&names_hash); + hash_free(&st_hash); } private: HASH st_hash; @@ -713,6 +748,17 @@ private: /* + A registry for item tree transformations performed during + query optimization. We register only those changes which require + a rollback to re-execute a prepared statement or stored procedure + yet another time. +*/ + +struct Item_change_record; +typedef I_List<Item_change_record> Item_change_list; + + +/* For each client connection we create a separate thread with THD serving as a thread/connection descriptor */ @@ -744,24 +790,6 @@ public: struct system_variables variables; // Changeable local variables struct system_status_var status_var; // Per thread statistic vars pthread_mutex_t LOCK_delete; // Locked before thd is deleted - /* - Note that (A) if we set query = NULL, we must at the same time set - query_length = 0, and protect the whole operation with the - LOCK_thread_count mutex. And (B) we are ONLY allowed to set query to a - non-NULL value if its previous value is NULL. We do not need to protect - operation (B) with any mutex. To avoid crashes in races, if we do not - know that thd->query cannot change at the moment, one should print - thd->query like this: - (1) reserve the LOCK_thread_count mutex; - (2) check if thd->query is NULL; - (3) if not NULL, then print at most thd->query_length characters from - it. We will see the query_length field as either 0, or the right value - for it. - Assuming that the write and read of an n-bit memory field in an n-bit - computer is atomic, we can avoid races in the above way. - This printing is needed at least in SHOW PROCESSLIST and SHOW INNODB - STATUS. - */ /* all prepared statements and cursors of this connection */ Statement_map stmt_map; /* @@ -831,6 +859,7 @@ public: */ MYSQL_LOCK *lock; /* Current locks */ MYSQL_LOCK *locked_tables; /* Tables locked with LOCK */ + HASH handler_tables_hash; /* One thread can hold up to one named user-level lock. This variable points to a lock object if the lock is present. See item_func.cc and @@ -864,10 +893,10 @@ public: THD_TRANS all; // Trans since BEGIN WORK THD_TRANS stmt; // Trans for current statement uint bdb_lock_count; - uint ndb_lock_count; #ifdef HAVE_NDBCLUSTER_DB - void* ndb; + void* thd_ndb; #endif + bool on; /* Tables changed in transaction (that must be invalidated in query cache). List contain only transactional tables, that not invalidated in query @@ -889,6 +918,14 @@ public: Vio* active_vio; #endif /* + This is to track items changed during execution of a prepared + statement/stored procedure. It's created by + register_item_tree_change() in memory root of THD, and freed in + rollback_item_tree_changes(). For conventional execution it's always 0. + */ + Item_change_list change_list; + + /* Current prepared Item_arena if there one, or 0 */ Item_arena *current_arena; @@ -1126,6 +1163,23 @@ public: } inline CHARSET_INFO *charset() { return variables.character_set_client; } void update_charset(); + + void change_item_tree(Item **place, Item *new_value) + { + /* TODO: check for OOM condition here */ + if (!current_arena->is_conventional_execution()) + nocheck_register_item_tree_change(place, *place, &mem_root); + *place= new_value; + } + void nocheck_register_item_tree_change(Item **place, Item *old_value, + MEM_ROOT *runtime_memroot); + void rollback_item_tree_changes(); + + /* + Cleanup statement parse state (parse tree, lex) and execution + state after execution of a non-prepared SQL statement. + */ + void end_statement(); inline int killed_errno() const { return killed != KILL_BAD_DATA ? killed : 0; @@ -1150,27 +1204,6 @@ public: #define SYSTEM_THREAD_SLAVE_SQL 4 /* - Disables binary logging for one thread, and resets it back to what it was - before being disabled. - Some functions (like the internal mysql_create_table() when it's called by - mysql_alter_table()) must NOT write to the binlog (binlogging is done at the - at a later stage of the command already, and must be, for locking reasons); - so we internally disable it temporarily by creating the Disable_binlog - object and reset the state by destroying the object (don't forget that! or - write code so that the object gets automatically destroyed when leaving a - block, see example in sql_table.cc). -*/ -class Disable_binlog { -private: - THD *thd; - ulong save_options; -public: - Disable_binlog(THD *thd_arg); - ~Disable_binlog(); -}; - - -/* Used to hold information about file and file structure in exchainge via non-DB file (...INTO OUTFILE..., ...LOAD DATA...) XXX: We never call destructor for objects of this class. @@ -1209,6 +1242,13 @@ public: unit= u; return 0; } + /* + Because of peculiarities of prepared statements protocol + we need to know number of columns in the result set (if + there is a result set) apart from sending columns metadata. + */ + virtual uint field_count(List<Item> &fields) const + { return fields.elements; } virtual bool send_fields(List<Item> &list, uint flags)=0; virtual bool send_data(List<Item> &items)=0; virtual bool initialize_tables (JOIN *join=0) { return 0; } @@ -1223,6 +1263,20 @@ public: }; +/* + Base class for select_result descendands which intercept and + transform result set rows. As the rows are not sent to the client, + sending of result set metadata should be suppressed as well. +*/ + +class select_result_interceptor: public select_result +{ +public: + uint field_count(List<Item> &fields) const { return 0; } + bool send_fields(List<Item> &fields, uint flag) { return FALSE; } +}; + + class select_send :public select_result { public: select_send() {} @@ -1232,7 +1286,7 @@ public: }; -class select_to_file :public select_result { +class select_to_file :public select_result_interceptor { protected: sql_exchange *exchange; File file; @@ -1244,7 +1298,6 @@ public: select_to_file(sql_exchange *ex) :exchange(ex), file(-1),row_count(0L) { path[0]=0; } ~select_to_file(); - bool send_fields(List<Item> &list, uint flags) { return 0; } void send_error(uint errcode,const char *err); bool send_eof(); void cleanup(); @@ -1271,7 +1324,7 @@ public: }; -class select_insert :public select_result { +class select_insert :public select_result_interceptor { public: TABLE_LIST *table_list; TABLE *table; @@ -1285,7 +1338,6 @@ class select_insert :public select_result { bool ignore_check_option_errors); ~select_insert(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items); void send_error(uint errcode,const char *err); bool send_eof(); @@ -1345,10 +1397,12 @@ public: uint group_parts,group_length,group_null_parts; uint quick_group; bool using_indirect_summary_function; + /* If >0 convert all blob fields to varchar(convert_blob_length) */ + uint convert_blob_length; TMP_TABLE_PARAM() :copy_funcs_it(copy_funcs), copy_field(0), group_parts(0), - group_length(0), group_null_parts(0) + group_length(0), group_null_parts(0), convert_blob_length(0) {} ~TMP_TABLE_PARAM() { @@ -1365,7 +1419,7 @@ public: } }; -class select_union :public select_result { +class select_union :public select_result_interceptor { public: TABLE *table; COPY_INFO info; @@ -1374,7 +1428,6 @@ class select_union :public select_result { select_union(TABLE *table_par); ~select_union(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items); bool send_eof(); bool flush(); @@ -1382,17 +1435,14 @@ class select_union :public select_result { }; /* Base subselect interface class */ -class select_subselect :public select_result +class select_subselect :public select_result_interceptor { protected: Item_subselect *item; public: select_subselect(Item_subselect *item); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items)=0; bool send_eof() { return 0; }; - - friend class Ttem_subselect; }; /* Single value subselect interface class */ @@ -1544,7 +1594,7 @@ public: }; -class multi_delete :public select_result +class multi_delete :public select_result_interceptor { TABLE_LIST *delete_tables, *table_being_deleted; Unique **tempfiles; @@ -1557,7 +1607,6 @@ public: multi_delete(THD *thd, TABLE_LIST *dt, uint num_of_tables); ~multi_delete(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items); bool initialize_tables (JOIN *join); void send_error(uint errcode,const char *err); @@ -1566,7 +1615,7 @@ public: }; -class multi_update :public select_result +class multi_update :public select_result_interceptor { TABLE_LIST *all_tables, *update_tables, *table_being_updated; THD *thd; @@ -1585,7 +1634,6 @@ public: List<Item> *values, enum_duplicates handle_duplicates); ~multi_update(); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items); bool initialize_tables (JOIN *join); void send_error(uint errcode,const char *err); @@ -1605,7 +1653,7 @@ public: ~my_var() {} }; -class select_dumpvar :public select_result { +class select_dumpvar :public select_result_interceptor { ha_rows row_count; public: List<my_var> var_list; @@ -1614,7 +1662,6 @@ public: select_dumpvar(void) { var_list.empty(); local_vars.empty(); vars.empty(); row_count=0;} ~select_dumpvar() {} int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - bool send_fields(List<Item> &list, uint flags) { return 0; } bool send_data(List<Item> &items); bool send_eof(); void cleanup(); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index f41e03b0602..982ad4a34a9 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -29,12 +29,12 @@ const char *del_exts[]= {".frm", ".BAK", ".TMD",".opt", NullS}; static TYPELIB deletable_extentions= -{array_elements(del_exts)-1,"del_exts", del_exts}; +{array_elements(del_exts)-1,"del_exts", del_exts, NULL}; const char *known_exts[]= {".ISM",".ISD",".ISM",".MRG",".MYI",".MYD",".db", ".ibd", NullS}; static TYPELIB known_extentions= -{array_elements(known_exts)-1,"known_exts", known_exts}; +{array_elements(known_exts)-1,"known_exts", known_exts, NULL}; static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, const char *path, @@ -966,6 +966,11 @@ err: communication packet (in case of 'connect' or 'COM_INIT_DB') we have to do end space removal in this function. + NOTES + Do as little as possible in this function, as it is not called for the + replication slave SQL thread (for that thread, setting of thd->db is done + in ::exec_event() methods of log_event.cc). + RETURN VALUES 0 ok 1 error diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index f9dba49d2e3..37121e64ecc 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -429,6 +429,8 @@ multi_delete::initialize_tables(JOIN *join) walk= walk->next_local; /* Don't use KEYREAD optimization on this table */ tbl->no_keyread=1; + /* Don't use record cache */ + tbl->no_cache= 1; tbl->used_keys.clear_all(); if (tbl->file->has_transactions()) log_delayed= transactional_tables= 1; @@ -744,8 +746,11 @@ int mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) { /* Probably InnoDB table */ table_list->lock_type= TL_WRITE; - DBUG_RETURN(mysql_delete(thd, table_list, (COND*) 0, (SQL_LIST*) 0, - HA_POS_ERROR, 0)); + ha_enable_transaction(thd, FALSE); + error= mysql_delete(thd, table_list, (COND*) 0, (SQL_LIST*) 0, + HA_POS_ERROR, 0); + ha_enable_transaction(thd, TRUE); + DBUG_RETURN(error); } if (lock_and_wait_for_table_name(thd, table_list)) DBUG_RETURN(-1); diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 5c827741e6d..0923cf811f5 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -222,7 +222,10 @@ static int mysql_derived(THD *thd, LEX *lex, SELECT_LEX_UNIT *unit, } } else + { free_tmp_table(thd, table); + unit->cleanup(); + } exit: delete derived_result; diff --git a/sql/sql_error.cc b/sql/sql_error.cc index c0f76ab0388..c09bfe0aea8 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -103,12 +103,11 @@ void mysql_reset_errors(THD *thd) MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level, uint code, const char *msg) { - MYSQL_ERROR *err= NULL; + MYSQL_ERROR *err= 0; DBUG_ENTER("push_warning"); if (thd->query_id != thd->warn_id) mysql_reset_errors(thd); - if (thd->spcont && thd->spcont->find_handler(code, ((int) level >= diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 38bc4756f81..61e57362bd4 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -1,5 +1,4 @@ -/* Copyright (C) 2000-2003 MySQL AB - +/* Copyright (C) 2000-2004 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -17,9 +16,6 @@ /* HANDLER ... commands - direct access to ISAM */ -#include "mysql_priv.h" -#include "sql_select.h" - /* TODO: HANDLER blabla OPEN [ AS foobar ] [ (column-list) ] @@ -37,195 +33,391 @@ all the sql_alloc'ed memory. It's harder to work around... */ +/* + There are two containers holding information about open handler tables. + The first is 'thd->handler_tables'. It is a linked list of TABLE objects. + It is used like 'thd->open_tables' in the table cache. The trick is to + exchange these two lists during open and lock of tables. Thus the normal + table cache code can be used. + The second container is a HASH. It holds objects of the type TABLE_LIST. + Despite its name, no lists of tables but only single structs are hashed + (the 'next' pointer is always NULL). The reason for theis second container + is, that we want handler tables to survive FLUSH TABLE commands. A table + affected by FLUSH TABLE must be closed so that other threads are not + blocked by handler tables still in use. Since we use the normal table cache + functions with 'thd->handler_tables', the closed tables are removed from + this list. Hence we need the original open information for the handler + table in the case that it is used again. This information is handed over + to mysql_ha_open() as a TABLE_LIST. So we store this information in the + second container, where it is not affected by FLUSH TABLE. The second + container is implemented as a hash for performance reasons. Consequently, + we use it not only for re-opening a handler table, but also for the + HANDLER ... READ commands. For this purpose, we store a pointer to the + TABLE structure (in the first container) in the TBALE_LIST object in the + second container. When the table is flushed, the pointer is cleared. +*/ + +#include "mysql_priv.h" +#include "sql_select.h" +#include <assert.h> + +#define HANDLER_TABLES_HASH_SIZE 120 + +static enum enum_ha_read_modes rkey_to_rnext[]= +{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV }; + #define HANDLER_TABLES_HACK(thd) { \ TABLE *tmp=thd->open_tables; \ thd->open_tables=thd->handler_tables; \ thd->handler_tables=tmp; } -static TABLE **find_table_ptr_by_name(THD *thd,const char *db, - const char *table_name, - bool is_alias, bool dont_lock, - bool *was_flushed); +static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags); + + +/* + Get hash key and hash key length. + + SYNOPSIS + mysql_ha_hash_get_key() + tables Pointer to the hash object. + key_len_p (out) Pointer to the result for key length. + first Unused. + + DESCRIPTION + The hash object is an TABLE_LIST struct. + The hash key is the alias name. + The hash key length is the alias name length plus one for the + terminateing NUL character. + + RETURN + Pointer to the TABLE_LIST struct. +*/ -int mysql_ha_open(THD *thd, TABLE_LIST *tables) +static char *mysql_ha_hash_get_key(TABLE_LIST *tables, uint *key_len_p, + my_bool first __attribute__((unused))) { - HANDLER_TABLES_HACK(thd); - uint counter; + *key_len_p= strlen(tables->alias) + 1 ; /* include '\0' in comparisons */ + return tables->alias; +} - /* for now HANDLER can be used only for real TABLES */ - tables->required_type= FRMTYPE_TABLE; - int err=open_tables(thd, tables, &counter); - HANDLER_TABLES_HACK(thd); - if (err) - return -1; +/* + Free an hash object. - // there can be only one table in *tables - if (!(tables->table->file->table_flags() & HA_CAN_SQL_HANDLER)) - { - my_printf_error(ER_ILLEGAL_HA,ER(ER_ILLEGAL_HA),MYF(0), tables->alias); - mysql_ha_close(thd, tables,1); - return -1; - } + SYNOPSIS + mysql_ha_hash_free() + tables Pointer to the hash object. - send_ok(thd); - return 0; + DESCRIPTION + The hash object is an TABLE_LIST struct. + + RETURN + Nothing +*/ + +static void mysql_ha_hash_free(TABLE_LIST *tables) +{ + my_free((char*) tables, MYF(0)); } /* - Close a HANDLER table. + Open a HANDLER table. SYNOPSIS - mysql_ha_close() + mysql_ha_open() thd Thread identifier. - tables A list of tables with the first entry to close. - dont_send_ok Suppresses the commands' ok message and - error message and error return. - dont_lock Suppresses the normal locking of LOCK_open. + tables A list of tables with the first entry to open. + reopen Re-open a previously opened handler table. DESCRIPTION Though this function takes a list of tables, only the first list entry - will be closed. Broadcasts a COND_refresh condition. - If mysql_ha_close() is not called from the parser, 'dont_send_ok' - must be set. - If the caller did already lock LOCK_open, it must set 'dont_lock'. - - IMPLEMENTATION - find_table_ptr_by_name() closes the table, if a FLUSH TABLE is outstanding. - It returns a NULL pointer in this case, but flags the situation in - 'was_flushed'. In that case the normal ER_UNKNOWN_TABLE error messages - is suppressed. + will be opened. + 'reopen' is set when a handler table is to be re-opened. In this case, + 'tables' is the pointer to the hashed TABLE_LIST object which has been + saved on the original open. + 'reopen' is also used to suppress the sending of an 'ok' message or + error messages. RETURN - 0 ok - -1 error + 0 ok + != 0 error */ -int mysql_ha_close(THD *thd, TABLE_LIST *tables, - bool dont_send_ok, bool dont_lock, bool no_alias) +int mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) { - TABLE **table_ptr; - bool was_flushed; - - table_ptr= find_table_ptr_by_name(thd, tables->db, tables->alias, - !no_alias, dont_lock, &was_flushed); - if (*table_ptr) + TABLE_LIST *hash_tables; + char *db, *name, *alias; + uint dblen, namelen, aliaslen, counter; + int error; + DBUG_ENTER("mysql_ha_open"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", + tables->db, tables->real_name, tables->alias, + (int) reopen)); + + if (! hash_inited(&thd->handler_tables_hash)) { - (*table_ptr)->file->ha_index_or_rnd_end(); - if (!dont_lock) - VOID(pthread_mutex_lock(&LOCK_open)); - if (close_thread_table(thd, table_ptr)) + /* + HASH entries are of type TABLE_LIST. + */ + if (hash_init(&thd->handler_tables_hash, &my_charset_latin1, + HANDLER_TABLES_HASH_SIZE, 0, 0, + (hash_get_key) mysql_ha_hash_get_key, + (hash_free_key) mysql_ha_hash_free, 0)) + goto err; + } + else if (! reopen) /* Otherwise we have 'tables' already. */ + { + if (hash_search(&thd->handler_tables_hash, (byte*) tables->alias, + strlen(tables->alias) + 1)) { - /* Tell threads waiting for refresh that something has happened */ - VOID(pthread_cond_broadcast(&COND_refresh)); + DBUG_PRINT("info",("duplicate '%s'", tables->alias)); + if (! reopen) + my_printf_error(ER_NONUNIQ_TABLE, ER(ER_NONUNIQ_TABLE), + MYF(0), tables->alias); + goto err; } - if (!dont_lock) - VOID(pthread_mutex_unlock(&LOCK_open)); } - else if (!was_flushed && !dont_send_ok) + + /* + open_tables() will set 'tables->table' if successful. + It must be NULL for a real open when calling open_tables(). + */ + DBUG_ASSERT(! tables->table); + HANDLER_TABLES_HACK(thd); + + /* for now HANDLER can be used only for real TABLES */ + tables->required_type= FRMTYPE_TABLE; + error= open_tables(thd, tables, &counter); + + HANDLER_TABLES_HACK(thd); + if (error) + goto err; + + /* There can be only one table in '*tables'. */ + if (! (tables->table->file->table_flags() & HA_CAN_SQL_HANDLER)) + { + if (! reopen) + my_printf_error(ER_ILLEGAL_HA,ER(ER_ILLEGAL_HA),MYF(0), tables->alias); + mysql_ha_close(thd, tables); + goto err; + } + + if (! reopen) { - my_printf_error(ER_UNKNOWN_TABLE, ER(ER_UNKNOWN_TABLE), MYF(0), - tables->alias, "HANDLER"); - return -1; + /* copy the TABLE_LIST struct */ + dblen= strlen(tables->db) + 1; + namelen= strlen(tables->real_name) + 1; + aliaslen= strlen(tables->alias) + 1; + if (!(my_multi_malloc(MYF(MY_WME), + &hash_tables, sizeof(*hash_tables), + &db, dblen, + &name, namelen, + &alias, aliaslen, + NullS))) + goto err; + /* structure copy */ + *hash_tables= *tables; + hash_tables->db= db; + hash_tables->real_name= name; + hash_tables->alias= alias; + memcpy(hash_tables->db, tables->db, dblen); + memcpy(hash_tables->real_name, tables->real_name, namelen); + memcpy(hash_tables->alias, tables->alias, aliaslen); + + /* add to hash */ + if (my_hash_insert(&thd->handler_tables_hash, (byte*) hash_tables)) + { + mysql_ha_close(thd, tables); + goto err; + } } - if (!dont_send_ok) + + if (! reopen) send_ok(thd); - return 0; + DBUG_PRINT("exit",("OK")); + DBUG_RETURN(0); + +err: + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(-1); } /* - Close a list of HANDLER tables. + Close a HANDLER table. SYNOPSIS - mysql_ha_close_list() + mysql_ha_close() thd Thread identifier. - tables The list of tables to close. If NULL, - close all HANDLER tables. - flushed Close only tables which are marked flushed. - Used only if tables is NULL. + tables A list of tables with the first entry to close. DESCRIPTION - The list of HANDLER tables may be NULL, in which case all HANDLER - tables are closed. Broadcasts a COND_refresh condition, for - every table closed. If 'tables' is NULL and 'flushed' is set, - all HANDLER tables marked for flush are closed. - The caller must lock LOCK_open. - - IMPLEMENTATION - find_table_ptr_by_name() closes the table, if it is marked for flush. - It returns a NULL pointer in this case, but flags the situation in - 'was_flushed'. In that case the normal ER_UNKNOWN_TABLE error messages - is suppressed. + Though this function takes a list of tables, only the first list entry + will be closed. Broadcasts a COND_refresh condition. RETURN - 0 ok + 0 ok + != 0 error */ -int mysql_ha_close_list(THD *thd, TABLE_LIST *tables, bool flushed) +int mysql_ha_close(THD *thd, TABLE_LIST *tables) { - TABLE_LIST *tl_item; + TABLE_LIST *hash_tables; TABLE **table_ptr; + DBUG_ENTER("mysql_ha_close"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s'", + tables->db, tables->real_name, tables->alias)); - if (tables) + if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, + (byte*) tables->alias, + strlen(tables->alias) + 1))) { - for (tl_item= tables ; tl_item; tl_item= tl_item->next_local) + /* + Though we could take the table pointer from hash_tables->table, + we must follow the thd->handler_tables chain anyway, as we need the + address of the 'next' pointer referencing this table + for close_thread_table(). + */ + for (table_ptr= &(thd->handler_tables); + *table_ptr && (*table_ptr != hash_tables->table); + table_ptr= &(*table_ptr)->next) + ; + + if (*table_ptr) { - mysql_ha_close(thd, tl_item, /*dont_send_ok*/ 1, - /*dont_lock*/ 1, /*no_alias*/ 1); + (*table_ptr)->file->ha_index_or_rnd_end(); + VOID(pthread_mutex_lock(&LOCK_open)); + table->file->ha_index_or_rnd_end(); + if (close_thread_table(thd, table_ptr)) + { + /* Tell threads waiting for refresh that something has happened */ + VOID(pthread_cond_broadcast(&COND_refresh)); + } + VOID(pthread_mutex_unlock(&LOCK_open)); } + hash_delete(&thd->handler_tables_hash, (byte*) hash_tables); } else { - table_ptr= &(thd->handler_tables); - while (*table_ptr) - { - if (! flushed || ((*table_ptr)->version != refresh_version)) - { - (*table_ptr)->file->ha_index_or_rnd_end(); - if (close_thread_table(thd, table_ptr)) - { - /* Tell threads waiting for refresh that something has happened */ - VOID(pthread_cond_broadcast(&COND_refresh)); - } - continue; - } - table_ptr= &((*table_ptr)->next); - } + my_printf_error(ER_UNKNOWN_TABLE, ER(ER_UNKNOWN_TABLE), MYF(0), + tables->alias, "HANDLER"); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(-1); } - return 0; + + send_ok(thd); + DBUG_PRINT("exit", ("OK")); + DBUG_RETURN(0); } -static enum enum_ha_read_modes rkey_to_rnext[]= -{ RNEXT_SAME, RNEXT, RPREV, RNEXT, RPREV, RNEXT, RPREV, RPREV }; +/* + Read from a HANDLER table. + + SYNOPSIS + mysql_ha_read() + thd Thread identifier. + tables A list of tables with the first entry to read. + mode + keyname + key_expr + ha_rkey_mode + cond + select_limit + offset_limit + RETURN + 0 ok + != 0 error +*/ + int mysql_ha_read(THD *thd, TABLE_LIST *tables, enum enum_ha_read_modes mode, char *keyname, List<Item> *key_expr, enum ha_rkey_function ha_rkey_mode, Item *cond, ha_rows select_limit,ha_rows offset_limit) { - int err, keyno=-1; - bool was_flushed; - TABLE *table= *find_table_ptr_by_name(thd, tables->db, tables->alias, - /*is_alias*/ 1, /*dont_lock*/ 0, - &was_flushed); + TABLE_LIST *hash_tables; + TABLE *table; + MYSQL_LOCK *lock; + List<Item> list; + Protocol *protocol= thd->protocol; + char buff[MAX_FIELD_WIDTH]; + String buffer(buff, sizeof(buff), system_charset_info); + int error, keyno= -1; + uint num_rows; + byte *key; + uint key_len; + DBUG_ENTER("mysql_ha_read"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s'", + tables->db, tables->real_name, tables->alias)); + + LINT_INIT(key); + LINT_INIT(key_len); + + list.push_front(new Item_field(NULL,NULL,"*")); + List_iterator<Item> it(list); + it++; + + if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, + (byte*) tables->alias, + strlen(tables->alias) + 1))) + { + table= hash_tables->table; + DBUG_PRINT("info-in-hash",("'%s'.'%s' as '%s' tab %p", + hash_tables->db, hash_tables->real_name, + hash_tables->alias, table)); + if (!table) + { + /* + The handler table has been closed. Re-open it. + */ + if (mysql_ha_open(thd, hash_tables, 1)) + { + DBUG_PRINT("exit",("reopen failed")); + goto err0; + } + + table= hash_tables->table; + DBUG_PRINT("info",("re-opened '%s'.'%s' as '%s' tab %p", + hash_tables->db, hash_tables->real_name, + hash_tables->alias, table)); + } + +#if MYSQL_VERSION_ID < 40100 + if (*tables->db && strcmp(table->table_cache_key, tables->db)) + { + DBUG_PRINT("info",("wrong db")); + table= NULL; + } +#endif + } + else + table= NULL; + if (!table) { - my_printf_error(ER_UNKNOWN_TABLE,ER(ER_UNKNOWN_TABLE),MYF(0), - tables->alias,"HANDLER"); - return -1; +#if MYSQL_VERSION_ID < 40100 + char buff[MAX_DBKEY_LENGTH]; + if (*tables->db) + strxnmov(buff, sizeof(buff), tables->db, ".", tables->real_name, NullS); + else + strncpy(buff, tables->alias, sizeof(buff)); + my_printf_error(ER_UNKNOWN_TABLE, ER(ER_UNKNOWN_TABLE), MYF(0), + buff, "HANDLER"); +#else + my_printf_error(ER_UNKNOWN_TABLE, ER(ER_UNKNOWN_TABLE), MYF(0), + tables->alias, "HANDLER"); +#endif + goto err0; } tables->table=table; if (cond && (cond->fix_fields(thd, tables, &cond) || cond->check_cols(1))) - return -1; - - /* InnoDB needs to know that this table handle is used in the HANDLER */ + goto err0; - table->file->init_table_handle_for_HANDLER(); + table->file->init_table_handle_for_HANDLER(); // Only InnoDB requires it if (keyname) { @@ -233,34 +425,22 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, { my_printf_error(ER_KEY_DOES_NOT_EXITS,ER(ER_KEY_DOES_NOT_EXITS),MYF(0), keyname,tables->alias); - return -1; + goto err0; } table->file->ha_index_or_rnd_end(); table->file->ha_index_init(keyno); } - List<Item> list; - list.push_front(new Item_field(NULL,NULL,"*")); - List_iterator<Item> it(list); - Protocol *protocol= thd->protocol; - char buff[MAX_FIELD_WIDTH]; - String buffer(buff, sizeof(buff), system_charset_info); - uint num_rows; - byte *key; - uint key_len; - LINT_INIT(key); - LINT_INIT(key_len); - - it++; // Skip first NULL field - - insert_fields(thd, tables, tables->db, tables->alias, &it, 0, 0); + if (insert_fields(thd, tables, tables->db, tables->alias, &it, 0, 0)) + goto err0; select_limit+=offset_limit; protocol->send_fields(&list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF); HANDLER_TABLES_HACK(thd); - MYSQL_LOCK *lock=mysql_lock_tables(thd,&tables->table,1); + lock= mysql_lock_tables(thd, &tables->table, 1); HANDLER_TABLES_HACK(thd); + if (!lock) goto err0; // mysql_lock_tables() printed error message already @@ -277,33 +457,33 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, switch (mode) { case RFIRST: if (keyname) - err=table->file->index_first(table->record[0]); + error= table->file->index_first(table->record[0]); else { table->file->ha_index_or_rnd_end(); - if (!(err=table->file->ha_rnd_init(1))) - err=table->file->rnd_next(table->record[0]); + if (!(error= table->file->ha_rnd_init(1))) + error= table->file->rnd_next(table->record[0]); } mode=RNEXT; break; case RLAST: DBUG_ASSERT(keyname != 0); - err=table->file->index_last(table->record[0]); + error= table->file->index_last(table->record[0]); mode=RPREV; break; case RNEXT: - err=keyname ? - table->file->index_next(table->record[0]) : - table->file->rnd_next(table->record[0]); - break; + error= (keyname ? + table->file->index_next(table->record[0]) : + table->file->rnd_next(table->record[0])); + break; case RPREV: DBUG_ASSERT(keyname != 0); - err=table->file->index_prev(table->record[0]); + error= table->file->index_prev(table->record[0]); break; case RNEXT_SAME: /* Continue scan on "(keypart1,keypart2,...)=(c1, c2, ...) */ DBUG_ASSERT(keyname != 0); - err= table->file->index_next_same(table->record[0], key, key_len); + error= table->file->index_next_same(table->record[0], key, key_len); break; case RKEY: { @@ -338,7 +518,7 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, goto err; } key_copy(key, table->record[0], table->key_info + keyno, key_len); - err=table->file->index_read(table->record[0], + error= table->file->index_read(table->record[0], key,key_len,ha_rkey_mode); mode=rkey_to_rnext[(int)ha_rkey_mode]; break; @@ -348,10 +528,10 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, goto err; } - if (err == HA_ERR_RECORD_DELETED) - continue; - if (err) + if (error) { + if (error == HA_ERR_RECORD_DELETED) + continue; if (err != HA_ERR_KEY_NOT_FOUND && err != HA_ERR_END_OF_FILE) { sql_print_error("mysql_ha_read: Got error %d when reading table '%s'", @@ -384,85 +564,152 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables, ok: mysql_unlock_tables(thd,lock); send_eof(thd); - return 0; + DBUG_PRINT("exit",("OK")); + DBUG_RETURN(0); + err: mysql_unlock_tables(thd,lock); err0: - return -1; + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(-1); } /* - Find a HANDLER table by name. + Flush (close) a list of HANDLER tables. SYNOPSIS - find_table_ptr_by_name() + mysql_ha_flush() thd Thread identifier. - db Database (schema) name. - table_name Table name ;-). - is_alias Table name may be an alias name. - dont_lock Suppresses the normal locking of LOCK_open. + tables The list of tables to close. If NULL, + close all HANDLER tables [marked as flushed]. + mode_flags MYSQL_HA_CLOSE_FINAL finally close the table. + MYSQL_HA_REOPEN_ON_USAGE mark for reopen. + MYSQL_HA_FLUSH_ALL flush all tables, not only + those marked for flush. DESCRIPTION - Find the table 'db'.'table_name' in the list of HANDLER tables of the - thread 'thd'. If the table has been marked by FLUSH TABLE(S), close it, - flag this situation in '*was_flushed' and broadcast a COND_refresh - condition. - An empty database (schema) name matches all database (schema) names. - If the caller did already lock LOCK_open, it must set 'dont_lock'. - - IMPLEMENTATION - Just in case that the table is twice in 'thd->handler_tables' (!?!), - the loop does not break when the table was flushed. If another table - by that name was found and not flushed, '*was_flushed' is cleared again, - since a pointer to an open HANDLER table is returned. + The list of HANDLER tables may be NULL, in which case all HANDLER + tables are closed (if MYSQL_HA_FLUSH_ALL) is set. + If 'tables' is NULL and MYSQL_HA_FLUSH_ALL is not set, + all HANDLER tables marked for flush are closed. + Broadcasts a COND_refresh condition, for every table closed. + The caller must lock LOCK_open. + + NOTE + Since mysql_ha_flush() is called when the base table has to be closed, + we compare real table names, not aliases. Hence, database names matter. RETURN - *was_flushed Table has been closed due to FLUSH TABLE. - NULL A HANDLER Table by that name does not exist (any more). - != NULL Pointer to the TABLE structure. + 0 ok */ -static TABLE **find_table_ptr_by_name(THD *thd, const char *db, - const char *table_name, - bool is_alias, bool dont_lock, - bool *was_flushed) +int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags) { - int dblen; - TABLE **table_ptr; - - DBUG_ASSERT(db); - dblen= strlen(db); - table_ptr= &(thd->handler_tables); - *was_flushed= FALSE; + TABLE_LIST *tmp_tables; + TABLE **table_ptr; + DBUG_ENTER("mysql_ha_flush"); + DBUG_PRINT("enter", ("tables: %p mode_flags: 0x%02x", tables, mode_flags)); - for (TABLE *table= *table_ptr; table ; table= *table_ptr) + if (tables) { - if ((db == any_db || !memcmp(table->table_cache_key, db, dblen)) && - !my_strcasecmp(system_charset_info, - (is_alias ? table->table_name : table->real_name), - table_name)) + /* Close all tables in the list. */ + for (tmp_tables= tables ; tmp_tables; tmp_tables= tmp_tables->next_local) { - if (table->version != refresh_version) + DBUG_PRINT("info-in-tables-list",("'%s'.'%s' as '%s'", + tmp_tables->db, tmp_tables->real_name, + tmp_tables->alias)); + /* Close all currently open handler tables with the same base table. */ + table_ptr= &(thd->handler_tables); + while (*table_ptr) { - if (!dont_lock) - VOID(pthread_mutex_lock(&LOCK_open)); - - table->file->ha_index_or_rnd_end(); - if (close_thread_table(thd, table_ptr)) + if ((! *tmp_tables->db || + ! my_strcasecmp(&my_charset_latin1, (*table_ptr)->table_cache_key, + tmp_tables->db)) && + ! my_strcasecmp(&my_charset_latin1, (*table_ptr)->real_name, + tmp_tables->real_name)) { - /* Tell threads waiting for refresh that something has happened */ - VOID(pthread_cond_broadcast(&COND_refresh)); + DBUG_PRINT("info",("*table_ptr '%s'.'%s' as '%s'", + (*table_ptr)->table_cache_key, + (*table_ptr)->real_name, + (*table_ptr)->table_name)); + mysql_ha_flush_table(thd, table_ptr, mode_flags); + continue; } - if (!dont_lock) - VOID(pthread_mutex_unlock(&LOCK_open)); - *was_flushed= TRUE; + table_ptr= &(*table_ptr)->next; + } + /* end of handler_tables list */ + } + /* end of flush tables list */ + } + else + { + /* Close all currently open tables [which are marked for flush]. */ + table_ptr= &(thd->handler_tables); + while (*table_ptr) + { + if ((mode_flags & MYSQL_HA_FLUSH_ALL) || + ((*table_ptr)->version != refresh_version)) + { + mysql_ha_flush_table(thd, table_ptr, mode_flags); continue; } - *was_flushed= FALSE; - break; + table_ptr= &(*table_ptr)->next; + } + } + + DBUG_RETURN(0); +} + +/* + Flush (close) a table. + + SYNOPSIS + mysql_ha_flush_table() + thd Thread identifier. + table The table to close. + mode_flags MYSQL_HA_CLOSE_FINAL finally close the table. + MYSQL_HA_REOPEN_ON_USAGE mark for reopen. + + DESCRIPTION + Broadcasts a COND_refresh condition, for every table closed. + The caller must lock LOCK_open. + + RETURN + 0 ok +*/ + +static int mysql_ha_flush_table(THD *thd, TABLE **table_ptr, uint mode_flags) +{ + TABLE_LIST *hash_tables; + TABLE *table= *table_ptr; + DBUG_ENTER("mysql_ha_flush_table"); + DBUG_PRINT("enter",("'%s'.'%s' as '%s' flags: 0x%02x", + table->table_cache_key, table->real_name, + table->table_name, mode_flags)); + + if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash, + (byte*) (*table_ptr)->table_name, + strlen((*table_ptr)->table_name) + 1))) + { + if (! (mode_flags & MYSQL_HA_REOPEN_ON_USAGE)) + { + /* This is a final close. Remove from hash. */ + hash_delete(&thd->handler_tables_hash, (byte*) hash_tables); + } + else + { + /* Mark table as closed, ready for re-open. */ + hash_tables->table= NULL; } - table_ptr= &(table->next); + } + + (*table_ptr)->file->ha_index_or_rnd_end(); + if (close_thread_table(thd, table_ptr)) + { + /* Tell threads waiting for refresh that something has happened */ + VOID(pthread_cond_broadcast(&COND_refresh)); } - return table_ptr; + + DBUG_RETURN(0); } diff --git a/sql/sql_help.cc b/sql/sql_help.cc index cba74c93a6a..b349a09e49e 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -747,7 +747,7 @@ int mysqld_help(THD *thd, const char *mask) select,&subcategories_list); delete select; String *cat= categories_list.head(); - if (send_header_2(protocol, true) || + if (send_header_2(protocol, TRUE) || send_variant_2_list(mem_root,protocol,&topics_list, "N",cat) || send_variant_2_list(mem_root,protocol,&subcategories_list,"Y",cat)) goto end; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d9002c2da29..63e11822f6e 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -44,8 +44,8 @@ static void unlink_blobs(register TABLE *table); /* Check if insert fields are correct. - Sets table->timestamp_default_now/on_update_now to 0 o leaves it to point - to timestamp field, depending on if timestamp should be updated or not. + Sets table->timestamp_field_type to TIMESTAMP_NO_AUTO_SET or leaves it + as is, depending on if timestamp should be updated or not. */ static int @@ -74,7 +74,7 @@ check_insert_fields(THD *thd, TABLE_LIST *table_list, List<Item> &fields, return -1; } #endif - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; } else { // Part field list @@ -105,7 +105,7 @@ check_insert_fields(THD *thd, TABLE_LIST *table_list, List<Item> &fields, } if (table->timestamp_field && // Don't set timestamp if used table->timestamp_field->query_id == thd->query_id) - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; } // For the values we need select_priv #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -767,7 +767,8 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) */ if (last_uniq_key(table,key_nr) && !table->file->referenced_by_foreign_key() && - table->timestamp_default_now == table->timestamp_on_update_now) + (table->timestamp_field_type == TIMESTAMP_NO_AUTO_SET || + table->timestamp_field_type == TIMESTAMP_AUTO_SET_ON_BOTH)) { if ((error=table->file->update_row(table->record[1], table->record[0]))) @@ -845,8 +846,7 @@ public: time_t start_time; bool query_start_used,last_insert_id_used,insert_id_used, log_query; ulonglong last_insert_id; - ulong timestamp_default_now; - ulong timestamp_on_update_now; + timestamp_auto_set_type timestamp_field_type; uint query_length; delayed_row(enum_duplicates dup_arg, bool log_query_arg) @@ -1140,7 +1140,7 @@ TABLE *delayed_insert::get_local_table(THD* client_thd) copy->timestamp_field= (Field_timestamp*) copy->field[table->timestamp_field_offset]; copy->timestamp_field->unireg_check= table->timestamp_field->unireg_check; - copy->timestamp_field->set_timestamp_offsets(); + copy->timestamp_field_type= copy->timestamp_field->get_auto_set_type(); } /* _rowid is not used with delayed insert */ @@ -1195,8 +1195,7 @@ static int write_delayed(THD *thd,TABLE *table,enum_duplicates duplic, row->last_insert_id_used= thd->last_insert_id_used; row->insert_id_used= thd->insert_id_used; row->last_insert_id= thd->last_insert_id; - row->timestamp_default_now= table->timestamp_default_now; - row->timestamp_on_update_now= table->timestamp_on_update_now; + row->timestamp_field_type= table->timestamp_field_type; di->rows.push_back(row); di->stacked_inserts++; @@ -1537,8 +1536,7 @@ bool delayed_insert::handle_inserts(void) thd.last_insert_id=row->last_insert_id; thd.last_insert_id_used=row->last_insert_id_used; thd.insert_id_used=row->insert_id_used; - table->timestamp_default_now= row->timestamp_default_now; - table->timestamp_on_update_now= row->timestamp_on_update_now; + table->timestamp_field_type= row->timestamp_field_type; info.handle_duplicates= row->dup; if (info.handle_duplicates == DUP_IGNORE || @@ -1892,7 +1890,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) field=table->field+table->fields - values.elements; /* Don't set timestamp if used */ - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; table->next_number_field=table->found_next_number_field; @@ -1949,7 +1947,13 @@ bool select_create::send_eof() We should be able to just keep the table in the table cache. */ if (!table->tmp_table) + { + ulong version= table->version; hash_delete(&open_cache,(byte*) table); + /* Tell threads waiting for refresh that something has happened */ + if (version != refresh_version) + VOID(pthread_cond_broadcast(&COND_refresh)); + } lock=0; table=0; VOID(pthread_mutex_unlock(&LOCK_open)); @@ -1971,9 +1975,13 @@ void select_create::abort() enum db_type table_type=table->db_type; if (!table->tmp_table) { + ulong version= table->version; hash_delete(&open_cache,(byte*) table); if (!create_info->table_existed) quick_rm_table(table_type, create_table->db, create_table->real_name); + /* Tell threads waiting for refresh that something has happened */ + if (version != refresh_version) + VOID(pthread_cond_broadcast(&COND_refresh)); } else if (!create_info->table_existed) close_temporary_table(thd, create_table->db, create_table->real_name); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 73f99d0dd68..d978cc14f64 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -124,7 +124,38 @@ void lex_free(void) void lex_start(THD *thd, uchar *buf,uint length) { LEX *lex= thd->lex; - lex->thd= thd; + lex->unit.init_query(); + lex->unit.init_select(); + lex->thd= lex->unit.thd= thd; + lex->select_lex.init_query(); + lex->value_list.empty(); + lex->param_list.empty(); + lex->view_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->select_lex.init_order(); + lex->select_lex.group_list.empty(); + lex->describe= 0; + lex->derived_tables= FALSE; + lex->view_prepare_mode= FALSE; + lex->lock_option= TL_READ; + lex->found_colon= 0; + lex->safe_to_cache_query= 1; + lex->time_zone_tables_used= 0; + lex->proc_table= lex->query_tables= 0; + lex->query_tables_last= &lex->query_tables; + lex->variables_used= 0; + lex->select_lex.parent_lex= lex; + lex->empty_field_list_on_rset= 0; + lex->select_lex.select_number= 1; lex->next_state=MY_LEX_START; lex->buf= lex->ptr= buf; lex->end_of_query=buf+length; @@ -533,6 +564,7 @@ int yylex(void *arg, void *yythd) /* Fall through */ case MY_LEX_IDENT_OR_BIN: // TODO: Add binary string handling case MY_LEX_IDENT: + uchar *start; #if defined(USE_MB) && defined(USE_MB_IDENT) if (use_mb(cs)) { @@ -569,11 +601,16 @@ int yylex(void *arg, void *yythd) result_state= result_state & 0x80 ? IDENT_QUOTED : IDENT; } length= (uint) (lex->ptr - lex->tok_start)-1; + start= lex->ptr; if (lex->ignore_space) { - for (; state_map[c] == MY_LEX_SKIP ; c= yyGet()); + /* + If we find a space then this can't be an identifier. We notice this + below by checking start != lex->ptr. + */ + for (; state_map[c] == MY_LEX_SKIP ; c= yyGet()); } - if (c == '.' && ident_map[yyPeek()]) + if (start == lex->ptr && c == '.' && ident_map[yyPeek()]) lex->next_state=MY_LEX_IDENT_SEP; else { // '(' must follow directly if function @@ -1413,6 +1450,7 @@ bool st_select_lex::setup_ref_array(THD *thd, uint order_group_num) 1 - found 0 - OK (table did not found) */ + bool st_select_lex_unit::check_updateable(char *db, char *table) { for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select()) @@ -1423,8 +1461,8 @@ bool st_select_lex_unit::check_updateable(char *db, char *table) /* - Find db.table which will be updated in this select and - underlayed ones (except derived tables) + Find db.table which will be updated in this select and + underlying ones (except derived tables) SYNOPSIS st_select_lex::check_updateable() @@ -1435,11 +1473,30 @@ bool st_select_lex_unit::check_updateable(char *db, char *table) 1 - found 0 - OK (table did not found) */ + bool st_select_lex::check_updateable(char *db, char *table) { if (find_table_in_local_list(get_table_list(), db, table)) return 1; + return check_updateable_in_subqueries(db, table); +} + +/* + Find db.table which will be updated in underlying subqueries + + SYNOPSIS + st_select_lex::check_updateable_in_subqueries() + db - data base name + table - real table name + + RETURN + 1 - found + 0 - OK (table did not found) +*/ + +bool st_select_lex::check_updateable_in_subqueries(char *db, char *table) +{ for (SELECT_LEX_UNIT *un= first_inner_unit(); un; un= un->next_unit()) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index af5b0896fa5..f2d89fdb16e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -283,6 +283,7 @@ public: static void *operator new(size_t size, MEM_ROOT *mem_root) { return (void*) alloc_root(mem_root, (uint) size); } static void operator delete(void *ptr,size_t size) {} + static void operator delete(void *ptr,size_t size, MEM_ROOT *mem_root) {} st_select_lex_node(): linkage(UNSPECIFIED_TYPE) {} virtual ~st_select_lex_node() {} inline st_select_lex_node* get_master() { return master; } @@ -333,6 +334,7 @@ class THD; class select_result; class JOIN; class select_union; +class Procedure; class st_select_lex_unit: public st_select_lex_node { protected: TABLE_LIST result_table_list; @@ -379,6 +381,7 @@ public: st_select_lex *union_distinct; /* pointer to the last UNION DISTINCT */ bool describe; /* union exec() called for EXPLAIN */ + Procedure *last_procedure; /* Pointer to procedure, if such exists */ void init_query(); st_select_lex_unit* master_unit(); @@ -413,7 +416,7 @@ public: int change_result(select_subselect *result, select_subselect *old_result); void set_limit(st_select_lex *values, st_select_lex *sl); - friend void mysql_init_query(THD *thd, uchar *buf, uint length, bool lexonly); + friend void lex_start(THD *thd, uchar *buf, uint length); friend int subselect_union_engine::exec(); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -569,7 +572,7 @@ public: bool test_limit(); - friend void mysql_init_query(THD *thd, uchar *buf, uint length, bool lexonly); + friend void lex_start(THD *thd, uchar *buf, uint length); st_select_lex() {} void make_empty_select() { @@ -578,6 +581,7 @@ public: } bool setup_ref_array(THD *thd, uint order_group_num); bool check_updateable(char *db, char *table); + bool check_updateable_in_subqueries(char *db, char *table); void print(THD *thd, String *str); static void print_order(String *str, ORDER *order); void print_limit(THD *thd, String *str); @@ -687,7 +691,7 @@ typedef struct st_lex USER_RESOURCES mqh; ulong thread_id,type; enum_sql_command sql_command; - thr_lock_type lock_option; + thr_lock_type lock_option, multi_lock_option; enum SSL_type ssl_type; /* defined in violite.h */ enum my_lex_states next_state; enum enum_duplicates duplicates; diff --git a/sql/sql_list.h b/sql/sql_list.h index 40530314893..d8d3719d39d 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -41,6 +41,8 @@ public: static void *operator new(size_t size, MEM_ROOT *mem_root) { return (void*) alloc_root(mem_root, (uint) size); } static void operator delete(void *ptr, size_t size) { TRASH(ptr, size); } + static void operator delete(void *ptr, size_t size, MEM_ROOT *mem_root) + { TRASH(ptr, size); } static void operator delete[](void *ptr, size_t size) { TRASH(ptr, size); } #ifdef HAVE_purify bool dummy; diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 95025a8fd77..1735da6b717 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -197,17 +197,16 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, #ifdef DONT_ALLOW_FULL_LOAD_DATA_PATHS ex->file_name+=dirname_length(ex->file_name); #endif - if (!dirname_length(ex->file_name) && - strlen(ex->file_name)+strlen(mysql_real_data_home)+strlen(tdb)+3 < - FN_REFLEN) + if (!dirname_length(ex->file_name)) { - (void) sprintf(name,"%s%s/%s",mysql_real_data_home,tdb,ex->file_name); - unpack_filename(name,name); /* Convert to system format */ + strxnmov(name, FN_REFLEN, mysql_real_data_home, tdb, NullS); + (void) fn_format(name, ex->file_name, name, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME); } else { - my_load_path(name, ex->file_name, mysql_real_data_home); - unpack_filename(name, name); + (void) fn_format(name, ex->file_name, mysql_real_data_home, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME); #if !defined(__WIN__) && !defined(OS2) && ! defined(__NETWARE__) MY_STAT stat_info; if (!my_stat(name,&stat_info,MYF(MY_WME))) @@ -283,12 +282,13 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (!(error=test(read_info.error))) { if (use_timestamp) - table->timestamp_default_now= table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; table->next_number_field=table->found_next_number_field; if (handle_duplicates == DUP_IGNORE || handle_duplicates == DUP_REPLACE) table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); + ha_enable_transaction(thd, FALSE); table->file->start_bulk_insert((ha_rows) 0); table->copy_blobs=1; @@ -307,6 +307,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, ignore_check_option_errors); if (table->file->end_bulk_insert()) error=1; /* purecov: inspected */ + ha_enable_transaction(thd, TRUE); table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->next_number_field=0; } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 079d8994549..55e0d33cdde 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -76,7 +76,7 @@ const char *any_db="*any*"; // Special symbol for check_access const char *command_name[]={ "Sleep", "Quit", "Init DB", "Query", "Field List", "Create DB", "Drop DB", "Refresh", "Shutdown", "Statistics", "Processlist", - "Connect","Kill","Debug","Ping","Time","Delayed_insert","Change user", + "Connect","Kill","Debug","Ping","Time","Delayed insert","Change user", "Binlog Dump","Table Dump", "Connect Out", "Register Slave", "Prepare", "Prepare Execute", "Long Data", "Close stmt", "Reset stmt", "Set option", "Fetch", @@ -516,12 +516,17 @@ void free_max_user_conn(void) /* Mark all commands that somehow changes a table This is used to check number of updates / hour + + sql_command is actually set to SQLCOM_END sometimes + so we need the +1 to include it in the array. */ -char uc_update_queries[SQLCOM_END]; +char uc_update_queries[SQLCOM_END+1]; void init_update_queries(void) { + bzero((gptr) &uc_update_queries, sizeof(uc_update_queries)); + uc_update_queries[SQLCOM_CREATE_TABLE]=1; uc_update_queries[SQLCOM_CREATE_INDEX]=1; uc_update_queries[SQLCOM_ALTER_TABLE]=1; @@ -548,6 +553,7 @@ void init_update_queries(void) bool is_update_query(enum enum_sql_command command) { + DBUG_ASSERT(command >= 0 && command <= SQLCOM_END); return uc_update_queries[command]; } @@ -912,7 +918,7 @@ static int check_connection(THD *thd) 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); + return check_user(thd, COM_CONNECT, passwd, passwd_len, db, TRUE); } @@ -1019,6 +1025,10 @@ pthread_handler_decl(handle_one_connection,arg) net->compress=1; // Use compression thd->version= refresh_version; + thd->proc_info= 0; + thd->set_time(); + thd->init_for_queries(); + if (sys_init_connect.value_length && !(thd->master_access & SUPER_ACL)) { execute_init_command(thd, &sys_init_connect, &LOCK_sys_init_connect); @@ -1039,12 +1049,12 @@ pthread_handler_decl(handle_one_connection,arg) if (net->error && net->vio != 0 && net->report_error) { if (!thd->killed && thd->variables.log_warnings > 1) - sql_print_error(ER(ER_NEW_ABORTING_CONNECTION), - thd->thread_id,(thd->db ? thd->db : "unconnected"), - thd->user ? thd->user : "unauthenticated", - thd->host_or_ip, - (net->last_errno ? ER(net->last_errno) : - ER(ER_UNKNOWN_ERROR))); + sql_print_warning(ER(ER_NEW_ABORTING_CONNECTION), + thd->thread_id,(thd->db ? thd->db : "unconnected"), + thd->user ? thd->user : "unauthenticated", + thd->host_or_ip, + (net->last_errno ? ER(net->last_errno) : + ER(ER_UNKNOWN_ERROR))); send_error(thd,net->last_errno,NullS); statistic_increment(aborted_threads,&LOCK_status); } @@ -1599,6 +1609,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, case COM_CREATE_DB: // QQ: To be removed { char *db=thd->strdup(packet), *alias; + HA_CREATE_INFO create_info; statistic_increment(thd->status_var.com_stat[SQLCOM_CREATE_DB], &LOCK_status); @@ -1611,7 +1622,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, 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); + bzero(&create_info, sizeof(create_info)); + if (mysql_create_db(thd, (lower_case_table_names == 2 ? alias : db), + &create_info, 0) < 0) + send_error(thd, thd->killed ? ER_SERVER_SHUTDOWN : 0); break; } case COM_DROP_DB: // QQ: To be removed @@ -1633,7 +1647,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; } mysql_log.write(thd,command,db); - mysql_rm_db(thd, (lower_case_table_names == 2 ? alias : db), 0, 0); + if (mysql_rm_db(thd, (lower_case_table_names == 2 ? alias : db), + 0, 0) < 0) + send_error(thd, thd->killed ? ER_SERVER_SHUTDOWN : 0); break; } #ifndef EMBEDDED_LIBRARY @@ -1904,12 +1920,12 @@ mysql_execute_command(THD *thd) { int res= 0; LEX *lex= thd->lex; + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ + SELECT_LEX *select_lex= &lex->select_lex; /* first table of first SELECT_LEX */ - TABLE_LIST *first_table= (TABLE_LIST*) lex->select_lex.table_list.first; + TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; /* list of all tables in query */ TABLE_LIST *all_tables; - /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ - SELECT_LEX *select_lex= &lex->select_lex; /* most outer SELECT_LEX_UNIT of query */ SELECT_LEX_UNIT *unit= &lex->unit; DBUG_ENTER("mysql_execute_command"); @@ -2084,6 +2100,7 @@ mysql_execute_command(THD *thd) CHARSET_INFO *to_cs= thd->variables.collation_connection; bool need_conversion; user_var_entry *entry; + String *pstr= &str; uint32 unused; /* Convert @var contents to string in connection character set. Although @@ -2096,29 +2113,45 @@ mysql_execute_command(THD *thd) lex->prepared_stmt_code.length)) && entry->value) { - String *pstr; my_bool is_var_null; pstr= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC); + /* + NULL value of variable checked early as entry->value so here + we can't get NULL in normal conditions + */ DBUG_ASSERT(!is_var_null); if (!pstr) - send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_ASSERT(pstr == &str); + { + res= -1; + break; // EOM (error should be reported by allocator) + } } else + { + /* + variable absent or equal to NULL, so we need to set variable to + something reasonable to get readable error message during parsing + */ str.set("NULL", 4, &my_charset_latin1); + } + need_conversion= - String::needs_conversion(str.length(), str.charset(), to_cs, &unused); + String::needs_conversion(pstr->length(), pstr->charset(), + to_cs, &unused); - query_len= need_conversion? (str.length() * to_cs->mbmaxlen) : - str.length(); + query_len= need_conversion? (pstr->length() * to_cs->mbmaxlen) : + pstr->length(); if (!(query_str= alloc_root(&thd->mem_root, query_len+1))) - send_error(thd, ER_OUT_OF_RESOURCES); + { + res= -1; + break; // EOM (error should be reported by allocator) + } if (need_conversion) - query_len= copy_and_convert(query_str, query_len, to_cs, str.ptr(), - str.length(), str.charset()); + query_len= copy_and_convert(query_str, query_len, to_cs, pstr->ptr(), + pstr->length(), pstr->charset()); else - memcpy(query_str, str.ptr(), str.length()); + memcpy(query_str, pstr->ptr(), pstr->length()); query_str[query_len]= 0; } else @@ -2414,9 +2447,6 @@ mysql_execute_command(THD *thd) { select_result *result; - if (select_tables && - check_table_access(thd, SELECT_ACL, select_tables, 0)) - goto create_error; // Error message is given select_lex->options|= SELECT_NO_UNLOCK; unit->set_limit(select_lex, select_lex); @@ -2552,7 +2582,7 @@ unsent_create_error: if (lex->name && (!lex->name[0] || strlen(lex->name) > NAME_LEN)) { net_printf(thd, ER_WRONG_TABLE_NAME, lex->name); - res=0; + res= 1; break; } if (!select_lex->db) @@ -2775,12 +2805,11 @@ unsent_create_error: case SQLCOM_INSERT: { DBUG_ASSERT(first_table == all_tables && first_table != 0); - my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); - if ((res= insert_precheck(thd, all_tables, update))) + if ((res= insert_precheck(thd, all_tables))) break; res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values, select_lex->item_list, lex->value_list, - (update ? DUP_UPDATE : lex->duplicates)); + lex->duplicates); if (thd->net.report_error) res= -1; if (first_table->view && !first_table->contain_auto_increment) @@ -2895,10 +2924,8 @@ unsent_create_error: } thd->proc_info="init"; - if ((res= open_and_lock_tables(thd, all_tables))) - break; - - if ((res= mysql_multi_delete_prepare(thd))) + if ((res= open_and_lock_tables(thd, all_tables)) || + (res= mysql_multi_delete_prepare(thd))) break; if (!thd->is_fatal_error && (result= new multi_delete(thd,aux_tables, @@ -4134,6 +4161,7 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, thd->priv_user, db, test(want_access & GRANT_ACL)); else db_access=thd->db_access; + DBUG_PRINT("info",("db_access: %lu", db_access)); /* Remove SHOW attribute and access rights we already have */ want_access &= ~(thd->master_access | EXTRA_ACL); DBUG_PRINT("info",("db_access: %lu want_access: %lu", @@ -4206,7 +4234,10 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, TABLE_LIST *org_tables=tables; for (; tables; tables= tables->next_global) { - if (tables->derived || (tables->table && (int)tables->table->tmp_table)) + if (tables->derived || + (tables->table && (int)tables->table->tmp_table) || + my_tz_check_n_skip_implicit_tables(&tables, + thd->lex->time_zone_tables_used)) continue; if ((thd->master_access & want_access) == (want_access & ~EXTRA_ACL) && thd->db) @@ -4411,58 +4442,44 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize) ****************************************************************************/ void -mysql_init_query(THD *thd, uchar *buf, uint length, bool lexonly) +mysql_init_query(THD *thd, uchar *buf, uint length) { DBUG_ENTER("mysql_init_query"); - 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->view_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->select_lex.init_order(); - lex->select_lex.group_list.empty(); - lex->describe= 0; - lex->derived_tables= 0; - lex->view_prepare_mode= FALSE; - lex->lock_option= TL_READ; - lex->found_colon= 0; - lex->safe_to_cache_query= 1; - lex->time_zone_tables_used= 0; - lex->proc_table= lex->query_tables= 0; - lex->query_tables_last= &lex->query_tables; - lex->variables_used= 0; - lex->select_lex.parent_lex= lex; - lex->empty_field_list_on_rset= 0; lex_start(thd, buf, length); - if (! lexonly) - { - 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= thd->time_zone_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(); - } + mysql_reset_thd_for_next_command(thd); + DBUG_VOID_RETURN; +} + + +/* + Reset THD part responsible for command processing state. + + DESCRIPTION + This needs to be called before execution of every statement + (prepared or conventional). + + TODO + Make it a method of THD and align its name with the rest of + reset/end/start/init methods. + Call it after we use THD for queries, not before. +*/ + +void mysql_reset_thd_for_next_command(THD *thd) +{ + DBUG_ENTER("mysql_reset_thd_for_next_command"); + thd->free_list= 0; + thd->select_number= 1; + 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= thd->time_zone_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; } @@ -4647,6 +4664,7 @@ void mysql_parse(THD *thd, char *inBuf, uint length) thd->proc_info="freeing items"; thd->end_statement(); thd->cleanup_after_query(); + DBUG_ASSERT(thd->change_list.is_empty()); } DBUG_VOID_RETURN; } @@ -4679,6 +4697,32 @@ bool mysql_test_parse_for_slave(THD *thd, char *inBuf, uint length) #endif + +/* + Calculate interval lengths. + Strip trailing spaces from all strings. + After this function call: + - ENUM uses max_length + - SET uses tot_length. +*/ +void calculate_interval_lengths(THD *thd, TYPELIB *interval, + uint *max_length, uint *tot_length) +{ + const char **pos; + uint *len; + CHARSET_INFO *cs= thd->variables.character_set_client; + *max_length= *tot_length= 0; + for (pos= interval->type_names, len= interval->type_lengths; + *pos ; pos++, len++) + { + *len= (uint) strip_sp((char*) *pos); + uint length= cs->cset->numchars(cs, *pos, *pos + *len); + *tot_length+= length; + set_if_bigger(*max_length, length); + } +} + + /***************************************************************************** ** Store field definition for create ** Return 0 if ok @@ -4737,7 +4781,7 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, } else if (default_value->type() == Item::NULL_ITEM) { - default_value=0; + default_value= 0; if ((type_modifier & (NOT_NULL_FLAG | AUTO_INCREMENT_FLAG)) == NOT_NULL_FLAG) { @@ -4808,23 +4852,23 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, switch (type) { case FIELD_TYPE_TINY: - if (!length) new_field->length=3+sign_len; + if (!length) new_field->length=MAX_TINYINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case FIELD_TYPE_SHORT: - if (!length) new_field->length=5+sign_len; + if (!length) new_field->length=MAX_SMALLINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case FIELD_TYPE_INT24: - if (!length) new_field->length=8+sign_len; + if (!length) new_field->length=MAX_MEDIUMINT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case FIELD_TYPE_LONG: - if (!length) new_field->length=10+sign_len; + if (!length) new_field->length=MAX_INT_WIDTH+sign_len; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case FIELD_TYPE_LONGLONG: - if (!length) new_field->length=20; + if (!length) new_field->length=MAX_BIGINT_WIDTH; allowed_type_modifier= AUTO_INCREMENT_FLAG; break; case FIELD_TYPE_NULL: @@ -4939,7 +4983,7 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, new_field->length=((new_field->length+1)/2)*2; /* purecov: inspected */ new_field->length= min(new_field->length,14); /* purecov: inspected */ } - new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG | NOT_NULL_FLAG; + new_field->flags|= ZEROFILL_FLAG | UNSIGNED_FLAG; if (default_value) { /* Grammar allows only NOW() value for ON UPDATE clause */ @@ -4960,13 +5004,24 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, } 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 available. + /* + If we have default TIMESTAMP NOT NULL column without explicit DEFAULT + or ON UPDATE values then for the sake of compatiblity we should treat + this column as having DEFAULT NOW() ON UPDATE NOW() (when we don't + have another TIMESTAMP column with auto-set option before this one) + or DEFAULT 0 (in other cases). + So here we are setting TIMESTAMP_OLD_FIELD only temporary, and will + replace this value by TIMESTAMP_DNUN_FIELD or NONE later when + information about all TIMESTAMP fields in table will be availiable. + + If we have TIMESTAMP NULL column without explicit DEFAULT value + we treat it as having DEFAULT NULL attribute. */ - new_field->unireg_check= on_update_value?Field::TIMESTAMP_UN_FIELD: - Field::TIMESTAMP_OLD_FIELD; + new_field->unireg_check= (on_update_value ? + Field::TIMESTAMP_UN_FIELD : + (new_field->flags & NOT_NULL_FLAG ? + Field::TIMESTAMP_OLD_FIELD: + Field::NONE)); } break; case FIELD_TYPE_DATE: // Old date type @@ -4993,15 +5048,10 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, if (new_field->pack_length > 4) new_field->pack_length=8; new_field->interval=interval; - new_field->length=0; - for (const char **pos=interval->type_names; *pos ; pos++) - { - uint length= (uint) strip_sp((char*) *pos)+1; - CHARSET_INFO *cs= thd->variables.character_set_client; - length= cs->cset->numchars(cs, *pos, *pos+length); - new_field->length+= length; - } - new_field->length--; + uint dummy_max_length; + calculate_interval_lengths(thd, interval, + &dummy_max_length, &new_field->length); + new_field->length+= (interval->count - 1); set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1); if (default_value) { @@ -5012,8 +5062,9 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, thd->cuted_fields=0; String str,*res; res=default_value->val_str(&str); - (void) find_set(interval, res->ptr(), res->length(), ¬_used, - ¬_used2, ¬_used3); + (void) find_set(interval, res->ptr(), res->length(), + &my_charset_bin, + ¬_used, ¬_used2, ¬_used3); if (thd->cuted_fields) { net_printf(thd,ER_INVALID_DEFAULT,field_name); @@ -5026,14 +5077,10 @@ bool add_field_to_list(THD *thd, char *field_name, enum_field_types type, { new_field->interval=interval; new_field->pack_length=interval->count < 256 ? 1 : 2; // Should be safe - new_field->length=(uint) strip_sp((char*) interval->type_names[0]); - for (const char **pos=interval->type_names+1; *pos ; pos++) - { - uint length=(uint) strip_sp((char*) *pos); - CHARSET_INFO *cs= thd->variables.character_set_client; - length= cs->cset->numchars(cs, *pos, *pos+length); - set_if_bigger(new_field->length,length); - } + + uint dummy_tot_length; + calculate_interval_lengths(thd, interval, + &new_field->length, &dummy_tot_length); set_if_smaller(new_field->length,MAX_FIELD_WIDTH-1); if (default_value) { @@ -5225,6 +5272,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->db= empty_c_string; ptr->db_length= 0; } + if (thd->current_arena->is_stmt_prepare()) + ptr->db= thd->strdup(ptr->db); ptr->alias= alias_str; if (lower_case_table_names && table->table.length) @@ -5601,7 +5650,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, acl_reload(thd); grant_reload(thd); if (mqh_used) - reset_mqh(thd,(LEX_USER *) NULL,true); + reset_mqh(thd,(LEX_USER *) NULL,TRUE); } #endif if (options & REFRESH_LOG) @@ -5990,12 +6039,15 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) */ for (table= tables; table; table= table->next_local) { - 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))) + if (table->derived) + table->grant.privilege= SELECT_ACL; + else 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); table->table_in_first_from_clause= 1; @@ -6005,9 +6057,10 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) */ if (&lex->select_lex != lex->all_selects_list) { + DBUG_PRINT("info",("Checking sub query list")); for (table= tables; table; table= table->next_global) { - if (!table->table_in_first_from_clause) + if (!table->table_in_first_from_clause && table->derived) { if (check_access(thd, SELECT_ACL, table->db, &table->grant.privilege, 0, 0) || @@ -6094,7 +6147,7 @@ int multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) INSERT ... SELECT query pre-check SYNOPSIS - multi_delete_precheck() + insert_delete_precheck() thd Thread handler tables Global table list @@ -6183,13 +6236,14 @@ int delete_precheck(THD *thd, TABLE_LIST *tables) -1 error (message is not sent to user) */ -int insert_precheck(THD *thd, TABLE_LIST *tables, bool update) +int insert_precheck(THD *thd, TABLE_LIST *tables) { LEX *lex= thd->lex; DBUG_ENTER("insert_precheck"); - ulong privilege= (lex->duplicates == DUP_REPLACE ? - INSERT_ACL | DELETE_ACL : INSERT_ACL | update); + ulong privilege= INSERT_ACL | + (lex->duplicates == DUP_REPLACE ? DELETE_ACL : 0) | + (lex->duplicates == DUP_UPDATE ? UPDATE_ACL : 0); if (check_one_table_access(thd, privilege, tables)) DBUG_RETURN(1); @@ -6215,26 +6269,74 @@ int insert_precheck(THD *thd, TABLE_LIST *tables, bool update) 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; + SELECT_LEX *select_lex= &lex->select_lex; + ulong want_priv; + int error= 1; // Error message is given DBUG_ENTER("create_table_precheck"); - ulong want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? - CREATE_TMP_ACL : CREATE_ACL); + + 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); + goto err; + if (grant_option && want_priv != CREATE_TMP_ACL && + check_grant(thd, want_priv, create_table, 0, UINT_MAX, 0)) + goto err; + + if (select_lex->item_list.elements) + { + /* Check permissions for used tables in CREATE TABLE ... SELECT */ + + /* + Only do the check for PS, becasue we on execute we have to check that + against the opened tables to ensure we don't use a table that is part + of the view (which can only be done after the table has been opened). + */ + if (thd->current_arena->is_stmt_prepare()) + { + /* + For temporary tables we don't have to check if the created table exists + */ + if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && + find_real_table_in_list(tables, create_table->db, + create_table->real_name)) + { + net_printf(thd,ER_UPDATE_TABLE_USED, create_table->real_name); + + goto err; + } + if (lex->create_info.used_fields & HA_CREATE_USED_UNION) + { + TABLE_LIST *tab; + for (tab= tables; tab; tab= tab->next) + { + if (find_real_table_in_list((TABLE_LIST*) lex->create_info. + merge_list.first, + tables->db, tab->real_name)) + { + net_printf(thd, ER_UPDATE_TABLE_USED, tab->real_name); + goto err; + } + } + } + } + if (tables && check_table_access(thd, SELECT_ACL, tables,0)) + goto err; + } + error= 0; + +err: + DBUG_RETURN(error); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 982e00391ef..a9a81ed7b06 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -899,10 +899,9 @@ static int mysql_test_insert(Prepared_statement *stmt, List_iterator_fast<List_item> its(values_list); List_item *values; int res; - my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); DBUG_ENTER("mysql_test_insert"); - if ((res= insert_precheck(thd, table_list, update))) + if ((res= insert_precheck(thd, table_list))) DBUG_RETURN(res); /* @@ -1071,6 +1070,12 @@ static int mysql_test_select(Prepared_statement *stmt, DBUG_RETURN(1); #endif + if (!lex->result && !(lex->result= new (&stmt->mem_root) select_send)) + { + send_error(thd); + goto err; + } + if ((result= open_and_lock_tables(thd, tables))) { result= 1; // Error sent @@ -1096,9 +1101,21 @@ static int mysql_test_select(Prepared_statement *stmt, } else { - if (send_prep_stmt(stmt, lex->select_lex.item_list.elements) || - thd->protocol_simple.send_fields(&lex->select_lex.item_list, - Protocol::SEND_EOF) + /* Make copy of item list, as change_columns may change it */ + List<Item> fields(lex->select_lex.item_list); + + /* Change columns if a procedure like analyse() */ + if (unit->last_procedure && + unit->last_procedure->change_columns(fields)) + goto err_prep; + + /* + We can use lex->result as it should've been + prepared in unit->prepare call above. + */ + if (send_prep_stmt(stmt, lex->result->field_count(fields)) || + thd->protocol_simple.send_fields(fields, + Protocol::SEND_EOF) #ifndef EMBEDDED_LIBRARY || net_flush(&thd->net) #endif @@ -1398,8 +1415,7 @@ static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) res= mysql_test_insert(stmt, tables, lex->field_list, lex->many_values, select_lex->item_list, lex->value_list, - (lex->value_list.elements ? - DUP_UPDATE : lex->duplicates)); + lex->duplicates); break; case SQLCOM_UPDATE: @@ -1491,8 +1507,16 @@ error: static bool init_param_array(Prepared_statement *stmt) { LEX *lex= stmt->lex; + THD *thd= stmt->thd; if ((stmt->param_count= lex->param_list.elements)) { + if (stmt->param_count > (uint) UINT_MAX16) + { + /* Error code to be defined in 5.0 */ + send_error(thd, ER_UNKNOWN_ERROR, + "Prepared statement contains too many placeholders."); + return 1; + } Item_param **to; List_iterator<Item_param> param_iterator(lex->param_list); /* Use thd->mem_root as it points at statement mem_root */ @@ -1501,7 +1525,7 @@ static bool init_param_array(Prepared_statement *stmt) sizeof(Item_param*) * stmt->param_count); if (!stmt->param_array) { - send_error(stmt->thd, ER_OUT_OF_RESOURCES); + send_error(thd, ER_OUT_OF_RESOURCES); return 1; } for (to= stmt->param_array; @@ -1596,11 +1620,13 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, thd->current_arena= stmt; mysql_init_query(thd, (uchar *) thd->query, thd->query_length); + /* Reset warnings from previous command */ + mysql_reset_errors(thd); lex= thd->lex; lex->safe_to_cache_query= 0; error= yyparse((void *)thd) || thd->is_fatal_error || - init_param_array(stmt); + thd->net.report_error || init_param_array(stmt); /* While doing context analysis of the query (in send_prepare_results) we allocate a lot of additional memory: for open tables, JOINs, derived @@ -1628,6 +1654,7 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, thd->restore_backup_statement(stmt, &thd->stmt_backup); cleanup_items(stmt->free_list); close_thread_tables(thd); + thd->rollback_item_tree_changes(); thd->cleanup_after_query(); thd->current_arena= thd; @@ -1636,7 +1663,9 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, /* Statement map deletes statement on erase */ thd->stmt_map.erase(stmt); stmt= NULL; - /* error is sent inside yyparse/send_prepare_results */ + if (thd->net.report_error) + send_error(thd); + /* otherwise the error is sent inside yyparse/send_prepare_results */ } else { @@ -1781,6 +1810,8 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; } + DBUG_ASSERT(thd->free_list == NULL); + mysql_reset_thd_for_next_command(thd); if (flags & (ulong) CURSOR_TYPE_READ_ONLY) { if (stmt->lex->result) @@ -1821,7 +1852,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query)) goto set_params_data_err; #endif - DBUG_ASSERT(thd->free_list == NULL); thd->stmt_backup.set_statement(thd); thd->set_statement(stmt); thd->current_arena= stmt; @@ -1903,6 +1933,8 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) DBUG_VOID_RETURN; } + /* Must go before setting variables, as it clears thd->user_var_events */ + mysql_reset_thd_for_next_command(thd); thd->set_n_backup_statement(stmt, &thd->stmt_backup); thd->set_statement(stmt); if (stmt->set_params_from_vars(stmt, @@ -1949,8 +1981,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, transformations of the query tree (i.e. negations elimination). This should be done permanently on the parse tree of this statement. */ - if (stmt->state == Item_arena::PREPARED) - thd->current_arena= stmt; + thd->current_arena= stmt; if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),QUERY_PRIOR); @@ -1959,7 +1990,9 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, my_pthread_setprio(pthread_self(), WAIT_PRIOR); thd->lex->unit.cleanup(); + thd->current_arena= thd; cleanup_items(stmt->free_list); + thd->rollback_item_tree_changes(); reset_stmt_params(stmt); close_thread_tables(thd); // to close derived tables thd->set_statement(&thd->stmt_backup); @@ -2062,6 +2095,7 @@ void mysql_stmt_reset(THD *thd, char *packet) */ reset_stmt_params(stmt); + mysql_reset_thd_for_next_command(thd); send_ok(thd); DBUG_VOID_RETURN; diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 9e38a65d412..10f0c23f54d 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -14,8 +14,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -// Sasha Pachev <sasha@mysql.com> is currently in charge of this file - #include "mysql_priv.h" #ifdef HAVE_REPLICATION @@ -311,7 +309,7 @@ int purge_master_logs(THD* thd, const char* to_log) char search_file_name[FN_REFLEN]; if (!mysql_bin_log.is_open()) { - send_ok(current_thd); + send_ok(thd); return 0; } @@ -324,8 +322,13 @@ int purge_master_logs(THD* thd, const char* to_log) int purge_master_logs_before_date(THD* thd, time_t purge_time) { - int res = mysql_bin_log.purge_logs_before_date(purge_time); - return purge_error_message(thd ,res); + if (!mysql_bin_log.is_open()) + { + send_ok(thd); + return 0; + } + return purge_error_message(thd, + mysql_bin_log.purge_logs_before_date(purge_time)); } int test_for_non_eof_log_read_errors(int error, const char **errmsg) diff --git a/sql/sql_repl.h b/sql/sql_repl.h index c39ef90114d..66fdfb4c022 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -1,3 +1,19 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB & Sasha + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + #ifdef HAVE_REPLICATION #include "slave.h" diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 9ba191a3f3a..f4fbd03768d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -232,9 +232,7 @@ int handle_select(THD *thd, LEX *lex, select_result *result) /* Don't set res if it's -1 as we may want this later */ DBUG_PRINT("info",("res: %d report_error: %d", res, thd->net.report_error)); - if (thd->net.report_error) - res= 1; - if (unlikely(res)) + if (thd->net.report_error || res<0) { if (res > 0) result->send_error(0, NullS); @@ -333,7 +331,7 @@ JOIN::prepare(Item ***rref_pointer_array, if (having_fix_rc || thd->net.report_error) DBUG_RETURN(-1); /* purecov: inspected */ if (having->with_sum_func) - having->split_sum_func(ref_pointer_array, all_fields); + having->split_sum_func(thd, ref_pointer_array, all_fields); } if (!thd->lex->view_prepare_mode) @@ -6805,8 +6803,9 @@ static COND* substitute_for_best_equal_field(COND *cond, */ static void -change_cond_ref_to_const(I_List<COND_CMP> *save_list,Item *and_father, - Item *cond, Item *field, Item *value) +change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list, + Item *and_father, Item *cond, + Item *field, Item *value) { if (cond->type() == Item::COND_ITEM) { @@ -6815,7 +6814,7 @@ change_cond_ref_to_const(I_List<COND_CMP> *save_list,Item *and_father, List_iterator<Item> li(*((Item_cond*) cond)->argument_list()); Item *item; while ((item=li++)) - change_cond_ref_to_const(save_list,and_level ? cond : item, item, + change_cond_ref_to_const(thd, save_list,and_level ? cond : item, item, field, value); return; } @@ -6823,8 +6822,9 @@ change_cond_ref_to_const(I_List<COND_CMP> *save_list,Item *and_father, return; // Not a boolean function Item_bool_func2 *func= (Item_bool_func2*) cond; - Item *left_item= func->arguments()[0]; - Item *right_item= func->arguments()[1]; + Item **args= func->arguments(); + Item *left_item= args[0]; + Item *right_item= args[1]; Item_func::Functype functype= func->functype(); if (right_item->eq(field,0) && left_item != value && @@ -6835,7 +6835,7 @@ change_cond_ref_to_const(I_List<COND_CMP> *save_list,Item *and_father, Item *tmp=value->new_item(); if (tmp) { - func->arguments()[1] = tmp; + thd->change_item_tree(args + 1, tmp); func->update_used_tables(); if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC) && and_father != cond && !left_item->const_item()) @@ -6856,13 +6856,14 @@ change_cond_ref_to_const(I_List<COND_CMP> *save_list,Item *and_father, Item *tmp=value->new_item(); if (tmp) { - func->arguments()[0] = value = tmp; + thd->change_item_tree(args, tmp); + value= tmp; func->update_used_tables(); if ((functype == Item_func::EQ_FUNC || functype == Item_func::EQUAL_FUNC) && and_father != cond && !right_item->const_item()) { - func->arguments()[0] = func->arguments()[1]; // For easy check - func->arguments()[1] = value; + args[0]= args[1]; // For easy check + thd->change_item_tree(args + 1, value); cond->marker=1; COND_CMP *tmp2; if ((tmp2=new COND_CMP(and_father,func))) @@ -6908,8 +6909,8 @@ static Item *remove_additional_cond(Item* conds) } static void -propagate_cond_constants(I_List<COND_CMP> *save_list,COND *and_father, - COND *cond) +propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list, + COND *and_father, COND *cond) { if (cond->type() == Item::COND_ITEM) { @@ -6920,18 +6921,19 @@ propagate_cond_constants(I_List<COND_CMP> *save_list,COND *and_father, I_List<COND_CMP> save; while ((item=li++)) { - propagate_cond_constants(&save,and_level ? cond : item, item); + propagate_cond_constants(thd, &save,and_level ? cond : item, item); } if (and_level) { // Handle other found items I_List_iterator<COND_CMP> cond_itr(save); COND_CMP *cond_cmp; while ((cond_cmp=cond_itr++)) - if (!cond_cmp->cmp_func->arguments()[0]->const_item()) - change_cond_ref_to_const(&save,cond_cmp->and_level, - cond_cmp->and_level, - cond_cmp->cmp_func->arguments()[0], - cond_cmp->cmp_func->arguments()[1]); + { + Item **args= cond_cmp->cmp_func->arguments(); + if (!args[0]->const_item()) + change_cond_ref_to_const(thd, &save,cond_cmp->and_level, + cond_cmp->and_level, args[0], args[1]); + } } } else if (and_father != cond && !cond->marker) // In a AND group @@ -6941,29 +6943,25 @@ propagate_cond_constants(I_List<COND_CMP> *save_list,COND *and_father, ((Item_func*) cond)->functype() == Item_func::EQUAL_FUNC)) { Item_func_eq *func=(Item_func_eq*) cond; - bool left_const= func->arguments()[0]->const_item(); - bool right_const=func->arguments()[1]->const_item(); + Item **args= func->arguments(); + bool left_const= args[0]->const_item(); + bool right_const= args[1]->const_item(); if (!(left_const && right_const) && - (func->arguments()[0]->result_type() == - (func->arguments()[1]->result_type()))) + args[0]->result_type() == args[1]->result_type()) { if (right_const) { - func->arguments()[1]=resolve_const_item(func->arguments()[1], - func->arguments()[0]); + resolve_const_item(thd, &args[1], args[0]); func->update_used_tables(); - change_cond_ref_to_const(save_list,and_father,and_father, - func->arguments()[0], - func->arguments()[1]); + change_cond_ref_to_const(thd, save_list, and_father, and_father, + args[0], args[1]); } else if (left_const) { - func->arguments()[0]=resolve_const_item(func->arguments()[0], - func->arguments()[1]); + resolve_const_item(thd, &args[0], args[1]); func->update_used_tables(); - change_cond_ref_to_const(save_list,and_father,and_father, - func->arguments()[1], - func->arguments()[0]); + change_cond_ref_to_const(thd, save_list, and_father, and_father, + args[1], args[0]); } } } @@ -7225,7 +7223,7 @@ optimize_cond(JOIN *join, COND *conds, Item::cond_result *cond_value) { DBUG_EXECUTE("where", print_where(conds, "original");); /* change field = field to field = const for each found field = const */ - propagate_cond_constants((I_List<COND_CMP> *) 0,conds,conds); + propagate_cond_constants(thd, (I_List<COND_CMP> *) 0, conds, conds); /* Remove all instances of item == item Remove all and-levels where CONST item != CONST item @@ -7461,21 +7459,28 @@ const_expression_in_where(COND *cond, Item *comp_item, Item **const_item) the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error new_created field */ -static Field* create_tmp_field_from_field(THD *thd, - Field* org_field, - Item *item, - TABLE *table, - bool modify_item) + +static Field* create_tmp_field_from_field(THD *thd, Field* org_field, + Item *item, TABLE *table, + bool modify_item, + uint convert_blob_length) { Field *new_field; - // The following should always be true - if ((new_field= org_field->new_field(&thd->mem_root,table))) + if (convert_blob_length && org_field->flags & BLOB_FLAG) + new_field= new Field_varstring(convert_blob_length, org_field->maybe_null(), + org_field->field_name, table, + org_field->charset()); + else + new_field= org_field->new_field(&thd->mem_root, table); + if (new_field) { if (modify_item) ((Item_field *)item)->result_field= new_field; @@ -7506,16 +7511,16 @@ static Field* create_tmp_field_from_field(THD *thd, the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error new_created field */ -static Field* create_tmp_field_from_item(THD *thd, - Item *item, - TABLE *table, - Item ***copy_func, - bool modify_item) +static Field* create_tmp_field_from_item(THD *thd, Item *item, TABLE *table, + Item ***copy_func, bool modify_item, + uint convert_blob_length) { bool maybe_null=item->maybe_null; Field *new_field; @@ -7532,13 +7537,18 @@ static Field* create_tmp_field_from_item(THD *thd, break; case STRING_RESULT: if (item->max_length > 255) - new_field= new Field_blob(item->max_length, maybe_null, - item->name, table, - item->collation.collation); + { + if (convert_blob_length) + new_field= new Field_varstring(convert_blob_length, maybe_null, + item->name, table, + item->collation.collation); + else + new_field= new Field_blob(item->max_length, maybe_null, item->name, + table, item->collation.collation); + } else - new_field= new Field_string(item->max_length, maybe_null, - item->name, table, - item->collation.collation); + new_field= new Field_string(item->max_length, maybe_null, item->name, + table, item->collation.collation); break; case ROW_RESULT: default: @@ -7575,6 +7585,8 @@ static Field* create_tmp_field_from_item(THD *thd, the record in the original table. If modify_item is 0 then fill_record() will update the temporary table + convert_blob_length If >0 create a varstring(convert_blob_length) field + instead of blob. RETURN 0 on error @@ -7582,8 +7594,8 @@ static Field* create_tmp_field_from_item(THD *thd, */ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, - Item ***copy_func, Field **from_field, - bool group, bool modify_item) + Item ***copy_func, Field **from_field, + bool group, bool modify_item, uint convert_blob_length) { switch (type) { case Item::SUM_FUNC_ITEM: @@ -7618,8 +7630,15 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, item->name,table,item->unsigned_flag); case STRING_RESULT: if (item_sum->max_length > 255) - return new Field_blob(item_sum->max_length,maybe_null, - item->name,table,item->collation.collation); + { + if (convert_blob_length) + return new Field_varstring(convert_blob_length, maybe_null, + item->name, table, + item->collation.collation); + else + return new Field_blob(item_sum->max_length, maybe_null, item->name, + table, item->collation.collation); + } return new Field_string(item_sum->max_length,maybe_null, item->name,table,item->collation.collation); case ROW_RESULT: @@ -7636,8 +7655,8 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::DEFAULT_VALUE_ITEM: { Item_field *field= (Item_field*) item; - return create_tmp_field_from_field(thd, (*from_field= field->field), - item, table, modify_item); + return create_tmp_field_from_field(thd, (*from_field= field->field), item, + table, modify_item, convert_blob_length); } case Item::FUNC_ITEM: case Item::COND_ITEM: @@ -7652,14 +7671,16 @@ Field *create_tmp_field(THD *thd, TABLE *table,Item *item, Item::Type type, case Item::REF_ITEM: case Item::NULL_ITEM: case Item::VARBIN_ITEM: - return create_tmp_field_from_item(thd, item, table, - copy_func, modify_item); + return create_tmp_field_from_item(thd, item, table, copy_func, modify_item, + convert_blob_length); case Item::TYPE_HOLDER: { Field *example= ((Item_type_holder *)item)->example(); if (example) - return create_tmp_field_from_field(thd, example, item, table, 0); - return create_tmp_field_from_item(thd, item, table, copy_func, 0); + return create_tmp_field_from_field(thd, example, item, table, 0, + convert_blob_length); + return create_tmp_field_from_item(thd, item, table, copy_func, 0, + convert_blob_length); } default: // Dosen't have to be stored return 0; @@ -7818,12 +7839,14 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, ((Item_sum*) item)->result_field=0; for (i=0 ; i < ((Item_sum*) item)->arg_count ; i++) { - Item *arg= ((Item_sum*) item)->args[i]; + Item **argp= ((Item_sum*) item)->args + i; + Item *arg= *argp; if (!arg->const_item()) { Field *new_field= - create_tmp_field(thd, table,arg,arg->type(),©_func, - tmp_from_field, group != 0,not_all_columns); + create_tmp_field(thd, table, arg, arg->type(), ©_func, + tmp_from_field, group != 0,not_all_columns, + param->convert_blob_length); if (!new_field) goto err; // Should be OOM tmp_from_field++; @@ -7834,7 +7857,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, *blob_field++= new_field; blob_count++; } - ((Item_sum*) item)->args[i]= new Item_field(new_field); + thd->change_item_tree(argp, new Item_field(new_field)); if (!(new_field->flags & NOT_NULL_FLAG)) { null_count++; @@ -7842,7 +7865,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, new_field->maybe_null() is still false, it will be changed below. But we have to setup Item_field correctly */ - ((Item_sum*) item)->args[i]->maybe_null=1; + (*argp)->maybe_null=1; } } } @@ -7859,9 +7882,10 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields, We here distinguish between UNION and multi-table-updates by the fact that in the later case group is set to the row pointer. */ - Field *new_field=create_tmp_field(thd, table, item,type, ©_func, - tmp_from_field, group != 0, - not_all_columns || group !=0); + Field *new_field= create_tmp_field(thd, table, item, type, ©_func, + tmp_from_field, group != 0, + not_all_columns || group !=0, + param->convert_blob_length); if (!new_field) { if (thd->is_fatal_error) @@ -8569,7 +8593,7 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) DBUG_PRINT("error",("Error: do_select() failed")); } #endif - DBUG_RETURN(error || join->thd->net.report_error); + DBUG_RETURN(join->thd->net.report_error ? -1 : error); } @@ -9050,6 +9074,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { table->key_read=1; table->file->extra(HA_EXTRA_KEYREAD); + tab->index= tab->ref.key; } if ((error=join_read_const(tab))) { @@ -10037,10 +10062,23 @@ part_of_refkey(TABLE *table,Field *field) /***************************************************************************** Test if one can use the key to resolve ORDER BY - Returns: 1 if key is ok. - 0 if key can't be used - -1 if reverse key can be used - used_key_parts is set to key parts used if length != 0 + + SYNOPSIS + test_if_order_by_key() + order Sort order + table Table to sort + idx Index to check + used_key_parts Return value for used key parts. + + + NOTES + used_key_parts is set to correct key parts used if return value != 0 + (On other cases, used_key_part may be changed) + + RETURN + 1 key is ok. + 0 Key can't be used + -1 Reverse key can be used *****************************************************************************/ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, @@ -10069,13 +10107,17 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, DBUG_RETURN(0); /* set flag to 1 if we can use read-next on key, else to -1 */ - flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? 1 : -1); + flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? + 1 : -1); if (reverse && flag != reverse) DBUG_RETURN(0); reverse=flag; // Remember if reverse key_part++; } *used_key_parts= (uint) (key_part - table->key_info[idx].key_part); + if (reverse == -1 && !(table->file->index_flags(idx, *used_key_parts-1, 1) & + HA_READ_PREV)) + reverse= 0; // Index can't be used DBUG_RETURN(reverse); } @@ -10288,14 +10330,11 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if (!select->quick->reverse_sorted()) { int quick_type= select->quick->get_type(); - /* here used_key_parts >0 */ - if (!(table->file->index_flags(ref_key,used_key_parts-1, 1) - & HA_READ_PREV) || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || + if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - DBUG_RETURN(0); // Use filesort + DBUG_RETURN(0); // Use filesort /* ORDER BY range_key DESC */ QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC((QUICK_RANGE_SELECT*)(select->quick), @@ -10317,9 +10356,6 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, Use a traversal function that starts by reading the last row with key part (A) and then traverse the index backwards. */ - if (!(table->file->index_flags(ref_key,used_key_parts-1, 1) - & HA_READ_PREV)) - DBUG_RETURN(0); // Use filesort tab->read_first_record= join_read_last_key; tab->read_record.read_record= join_read_prev_same; /* fall through */ @@ -10365,7 +10401,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, if (keys.is_set(nr)) { int flag; - if ((flag=test_if_order_by_key(order, table, nr, ¬_used))) + if ((flag= test_if_order_by_key(order, table, nr, ¬_used))) { if (!no_changes) { @@ -11125,15 +11161,14 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,ORDER *order, List<Item> &fields, List<Item> &all_fields) { - Item *itemptr=*order->item; - if (itemptr->type() == Item::INT_ITEM) + Item *it= *order->item; + if (it->type() == Item::INT_ITEM) { /* Order by position */ - uint count= (uint) itemptr->val_int(); + uint count= (uint) it->val_int(); if (!count || count > fields.elements) { my_printf_error(ER_BAD_FIELD_ERROR,ER(ER_BAD_FIELD_ERROR), - MYF(0),itemptr->full_name(), - thd->where); + MYF(0), it->full_name(), thd->where); return 1; } order->item= ref_pointer_array + count - 1; @@ -11143,20 +11178,28 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, return 0; } uint counter; - Item **item= find_item_in_list(itemptr, fields, &counter, - REPORT_EXCEPT_NOT_FOUND); + bool unaliased; + Item **item= find_item_in_list(it, fields, &counter, + REPORT_EXCEPT_NOT_FOUND, &unaliased); if (!item) return 1; if (item != (Item **)not_found_item) { + /* + If we have found field not by its alias in select list but by its + original field name, we should additionaly check if we have conflict + for this name (in case if we would perform lookup in all tables). + */ + if (unaliased && !it->fixed && it->fix_fields(thd, tables, order->item)) + return 1; + order->item= ref_pointer_array + counter; order->in_field_list=1; return 0; } order->in_field_list=0; - Item *it= *order->item; /* We check it->fixed because Item_func_group_concat can put arguments for which fix_fields already was called. @@ -11285,10 +11328,11 @@ setup_new_fields(THD *thd,TABLE_LIST *tables,List<Item> &fields, thd->set_query_id=1; // Not really needed, but... uint counter; + bool not_used; for (; new_field ; new_field= new_field->next) { if ((item= find_item_in_list(*new_field->item, fields, &counter, - IGNORE_ERRORS))) + IGNORE_ERRORS, ¬_used))) new_field->item=item; /* Change to shared Item */ else { @@ -11646,6 +11690,8 @@ setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, } } else if ((pos->type() == Item::FUNC_ITEM || + pos->type() == Item::SUBSELECT_ITEM || + pos->type() == Item::CACHE_ITEM || pos->type() == Item::COND_ITEM) && !pos->with_sum_func) { // Save for send fields diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 968058f0cfc..28e9baa1c36 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1,4 +1,4 @@ -/* Copyright (C) 2000 MySQL AB +/* Copyright (C) 2000-2004 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ static const char *grant_names[]={ #ifndef NO_EMBEDDED_ACCESS_CHECKS static TYPELIB grant_types = { sizeof(grant_names)/sizeof(char **), "grant_types", - grant_names}; + grant_names, NULL}; #endif static int @@ -407,6 +407,7 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path, if (wild && !wild[0]) wild=0; + bzero((char*) &table_list,sizeof(table_list)); if (!(dirp = my_dir(path,MYF(MY_WME | (dir ? MY_WANT_STAT : 0))))) @@ -419,27 +420,23 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path, { /* Return databases */ #ifdef USE_SYMDIR char *ext; + char buff[FN_REFLEN]; if (my_use_symdir && !strcmp(ext=fn_ext(file->name), ".sym")) { /* Only show the sym file if it points to a directory */ - char buff[FN_REFLEN], *end; - MY_STAT status; + char *end; *ext=0; /* Remove extension */ unpack_dirname(buff, file->name); end= strend(buff); if (end != buff && end[-1] == FN_LIBCHAR) end[-1]= 0; // Remove end FN_LIBCHAR - if (!my_stat(buff, &status, MYF(0)) || - !MY_S_ISDIR(status.st_mode)) - continue; - } - else + if (!my_stat(buff, file->mystat, MYF(0))) + continue; + } #endif - { if (file->name[0] == '.' || !MY_S_ISDIR(file->mystat->st_mode) || (wild && wild_compare(file->name,wild,0))) continue; - } } else { @@ -478,6 +475,9 @@ mysql_find_files(THD *thd,List<char> *files, const char *db,const char *path, } DBUG_PRINT("info",("found: %d files", files->elements)); my_dirend(dirp); + + VOID(ha_find_files(thd,db,path,wild,dir,files)); + DBUG_RETURN(0); } @@ -761,10 +761,11 @@ mysqld_show_fields(THD *thd, TABLE_LIST *table_list,const char *wild, protocol->store(field->has_charset() ? field->charset()->name : "NULL", system_charset_info); /* - Although TIMESTAMP fields can't contain NULL as its value they + Even if TIMESTAMP field can't contain NULL as its value it will accept NULL if you will try to insert such value and will - convert it to current TIMESTAMP. So YES here means that NULL - is allowed for assignment but can't be returned. + convert NULL value to current TIMESTAMP. So YES here means + that NULL is allowed for assignment (but may be won't be + returned). */ pos=(byte*) ((flags & NOT_NULL_FLAG) && field->type() != FIELD_TYPE_TIMESTAMP ? @@ -1240,51 +1241,33 @@ static const char *require_quotes(const char *name, uint name_length) } -static void append_quoted_simple_identifier(String *packet, char quote_char, - const char *name, uint length) -{ - packet->append("e_char, 1, system_charset_info); - packet->append(name, length, system_charset_info); - packet->append("e_char, 1, system_charset_info); -} - - void append_identifier(THD *thd, String *packet, const char *name, uint length) { const char *name_end; char quote_char; + int q= get_quote_char_for_identifier(thd, name, length); - if (thd->variables.sql_mode & MODE_ANSI_QUOTES) - quote_char= '\"'; - else - quote_char= '`'; - - if (is_keyword(name,length)) + if (q == EOF) { - append_quoted_simple_identifier(packet, quote_char, name, length); - return; - } - - if (!require_quotes(name, length)) - { - if (!(thd->options & OPTION_QUOTE_SHOW_CREATE)) - packet->append(name, length, system_charset_info); - else - append_quoted_simple_identifier(packet, quote_char, name, length); + packet->append(name, length, system_charset_info); return; } - /* The identifier must be quoted as it includes a quote character */ + /* + The identifier must be quoted as it includes a quote character or + it's a keyword + */ packet->reserve(length*2 + 2); + quote_char= (char) q; packet->append("e_char, 1, system_charset_info); for (name_end= name+length ; name < name_end ; name+= length) { - char chr= *name; + uchar chr= (uchar) *name; length= my_mbcharlen(system_charset_info, chr); - if (length == 1 && chr == quote_char) + if (length == 1 && chr == (uchar) quote_char) packet->append("e_char, 1, system_charset_info); packet->append(name, length, packet->charset()); } @@ -1292,15 +1275,46 @@ append_identifier(THD *thd, String *packet, const char *name, uint length) } +/* + Get the quote character for displaying an identifier. + + SYNOPSIS + get_quote_char_for_identifier() + thd Thread handler + name name to quote + length length of name + + IMPLEMENTATION + If name is a keyword or includes a special character, then force + quoting. + Otherwise identifier is quoted only if the option OPTION_QUOTE_SHOW_CREATE + is set. + + RETURN + EOF No quote character is needed + # Quote character +*/ + +int get_quote_char_for_identifier(THD *thd, const char *name, uint length) +{ + if (!is_keyword(name,length) && + !require_quotes(name, length) && + !(thd->options & OPTION_QUOTE_SHOW_CREATE)) + return EOF; + if (thd->variables.sql_mode & MODE_ANSI_QUOTES) + return '"'; + return '`'; +} + + /* Append directory name (if exists) to CREATE INFO */ static void append_directory(THD *thd, String *packet, const char *dir_type, const char *filename) { - uint length; if (filename && !(thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)) { - length= dirname_length(filename); + uint length= dirname_length(filename); packet->append(' '); packet->append(dir_type); packet->append(" DIRECTORY='", 12); @@ -1362,6 +1376,8 @@ store_create_info(THD *thd, TABLE *table, String *packet) // check for surprises from the previous call to Field::sql_type() if (type.ptr() != tmp) type.set(tmp, sizeof(tmp), system_charset_info); + else + type.set_charset(system_charset_info); field->sql_type(type); packet->append(type.ptr(), type.length(), system_charset_info); @@ -1386,7 +1402,14 @@ store_create_info(THD *thd, TABLE *table, String *packet) if (flags & NOT_NULL_FLAG) packet->append(" NOT NULL", 9); - + else if (field->type() == FIELD_TYPE_TIMESTAMP) + { + /* + TIMESTAMP field require explicit NULL flag, because unlike + all other fields they are treated as NOT NULL by default. + */ + packet->append(" NULL", 5); + } /* Again we are using CURRENT_TIMESTAMP instead of NOW because it is diff --git a/sql/sql_string.cc b/sql/sql_string.cc index ba9431f27c4..7d0dd3ca566 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -331,19 +331,26 @@ bool String::set_or_copy_aligned(const char *str,uint32 arg_length, /* Copy with charset convertion */ bool String::copy(const char *str, uint32 arg_length, - CHARSET_INFO *from_cs, CHARSET_INFO *to_cs) + CHARSET_INFO *from_cs, CHARSET_INFO *to_cs, uint *errors) { uint32 offset; if (!needs_conversion(arg_length, from_cs, to_cs, &offset)) + { + if (errors) + *errors= 0; return copy(str, arg_length, to_cs); + } if ((from_cs == &my_charset_bin) && offset) + { + if (errors) + *errors= 0; return copy_aligned(str, arg_length, offset, to_cs); - + } uint32 new_length= to_cs->mbmaxlen*arg_length; if (alloc(new_length)) return TRUE; str_length=copy_and_convert((char*) Ptr, new_length, to_cs, - str, arg_length, from_cs); + str, arg_length, from_cs, errors); str_charset=to_cs; return FALSE; } @@ -537,7 +544,8 @@ uint32 String::numchars() int String::charpos(int i,uint32 offset) { - if (i<0) return i; + if (i <= 0) + return i; return str_charset->cset->charpos(str_charset,Ptr+offset,Ptr+str_length,i); } @@ -781,7 +789,8 @@ String *copy_if_not_alloced(String *to,String *from,uint32 from_length) uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, - const char *from, uint32 from_length, CHARSET_INFO *from_cs) + const char *from, uint32 from_length, CHARSET_INFO *from_cs, + uint *errors) { int cnvres; my_wc_t wc; @@ -792,6 +801,7 @@ copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, const uchar *) = from_cs->cset->mb_wc; int (*wc_mb)(struct charset_info_st *, my_wc_t, uchar *s, uchar *e)= to_cs->cset->wc_mb; + uint error_count= 0; while (1) { @@ -800,6 +810,7 @@ copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, from+= cnvres; else if (cnvres == MY_CS_ILSEQ) { + error_count++; from++; wc= '?'; } @@ -811,12 +822,15 @@ outp: to+= cnvres; else if (cnvres == MY_CS_ILUNI && wc != '?') { + error_count++; wc= '?'; goto outp; } else break; } + if (errors) + *errors= error_count; return (uint32) (to - to_start); } diff --git a/sql/sql_string.h b/sql/sql_string.h index ad32305e9b4..d6807fa83ad 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -29,7 +29,7 @@ int sortcmp(const String *a,const String *b, CHARSET_INFO *cs); String *copy_if_not_alloced(String *a,String *b,uint32 arg_length); uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, const char *from, uint32 from_length, - CHARSET_INFO *from_cs); + CHARSET_INFO *from_cs, uint *errors= 0); class String { @@ -71,7 +71,9 @@ public: } static void *operator new(size_t size, MEM_ROOT *mem_root) { return (void*) alloc_root(mem_root, (uint) size); } - static void operator delete(void *ptr_arg,size_t size) /*lint -e715 */ + static void operator delete(void *ptr_arg,size_t size) + {} + static void operator delete(void *ptr_arg,size_t size, MEM_ROOT *mem_root) {} ~String() { free(); } @@ -197,7 +199,7 @@ public: CHARSET_INFO *cs); bool set_or_copy_aligned(const char *s, uint32 arg_length, CHARSET_INFO *cs); bool copy(const char*s,uint32 arg_length, CHARSET_INFO *csfrom, - CHARSET_INFO *csto); + CHARSET_INFO *csto, uint *errors= 0); bool append(const String &s); bool append(const char *s); bool append(const char *s,uint32 arg_length); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 399fed00040..c359c0f5f54 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -29,7 +29,13 @@ #include <io.h> #endif -const char *primary_key_name= "PRIMARY"; +#define tmp_disable_binlog(A) \ + ulong save_options= (A)->options; \ + (A)->options&= ~OPTION_BIN_LOG; + +#define reenable_binlog(A) (A)->options= save_options; + +const char *primary_key_name="PRIMARY"; static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end); static char *make_unique_key_name(const char *field_name,KEY *start,KEY *end); @@ -193,7 +199,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { char *db=table->db; - mysql_ha_close(thd, table, /*dont_send_ok*/ 1, /*dont_lock*/ 1); + mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL); if (!close_temporary_table(thd, db, table->real_name)) { tmp_table_deleted=1; @@ -218,8 +224,9 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, strxmov(path, mysql_data_home, "/", db, "/", alias, reg_ext, NullS); (void) unpack_filename(path,path); } - if (drop_temporary || access(path,F_OK) || - (!drop_view && mysql_frm_type(path) != FRMTYPE_TABLE)) + if (drop_temporary || + (access(path,F_OK) && ha_create_table_from_engine(thd,db,alias,TRUE)) || + (!drop_view && mysql_frm_type(path) != FRMTYPE_TABLE)) { if (if_exists) push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, @@ -362,19 +369,19 @@ static int sort_keys(KEY *a, KEY *b) */ void check_duplicates_in_interval(const char *set_or_name, - const char *name, TYPELIB *typelib) + const char *name, TYPELIB *typelib, + CHARSET_INFO *cs) { - unsigned int old_count= typelib->count; - const char **old_type_names= typelib->type_names; - - old_count= typelib->count; - old_type_names= typelib->type_names; + TYPELIB tmp= *typelib; const char **cur_value= typelib->type_names; - for ( ; typelib->count > 1; cur_value++) + unsigned int *cur_length= typelib->type_lengths; + + for ( ; tmp.count > 1; cur_value++, cur_length++) { - typelib->type_names++; - typelib->count--; - if (find_type((char*)*cur_value,typelib,1)) + tmp.type_names++; + tmp.type_lengths++; + tmp.count--; + if (find_type2(&tmp, (const char*)*cur_value, *cur_length, cs)) { push_warning_printf(current_thd,MYSQL_ERROR::WARN_LEVEL_NOTE, ER_DUPLICATED_VALUE_IN_TYPE, @@ -382,8 +389,6 @@ void check_duplicates_in_interval(const char *set_or_name, name,*cur_value,set_or_name); } } - typelib->count= old_count; - typelib->type_names= old_type_names; } /* @@ -563,7 +568,8 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->pack_flag|=FIELDFLAG_BINARY; sql_field->unireg_check=Field::INTERVAL_FIELD; check_duplicates_in_interval("ENUM",sql_field->field_name, - sql_field->interval); + sql_field->interval, + sql_field->charset); break; case FIELD_TYPE_SET: sql_field->pack_flag=pack_length_to_packflag(sql_field->pack_length) | @@ -572,7 +578,8 @@ int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->pack_flag|=FIELDFLAG_BINARY; sql_field->unireg_check=Field::BIT_FIELD; check_duplicates_in_interval("SET",sql_field->field_name, - sql_field->interval); + sql_field->interval, + sql_field->charset); break; case FIELD_TYPE_DATE: // Rest of string types case FIELD_TYPE_NEWDATE: @@ -1238,8 +1245,8 @@ int mysql_create_table(THD *thd,const char *db, const char *table_name, { bool create_if_not_exists = create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS; - if (!create_table_from_handler(db, table_name, - create_if_not_exists)) + if (!ha_create_table_from_engine(thd, db, table_name, + create_if_not_exists)) { DBUG_PRINT("info", ("Table already existed in handler")); @@ -1344,10 +1351,9 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, MYSQL_LOCK **lock) { TABLE tmp_table; // Used during 'create_field()' - TABLE *table; + TABLE *table= 0; tmp_table.table_name=0; uint select_field_count= items->elements; - Disable_binlog disable_binlog(thd); DBUG_ENTER("create_table_from_items"); /* Add selected items to field list */ @@ -1368,7 +1374,7 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, field=item->tmp_table_field(&tmp_table); else field=create_tmp_field(thd, &tmp_table, item, item->type(), - (Item ***) 0, &tmp_field,0,0); + (Item ***) 0, &tmp_field,0,0,0); if (!field || !(cr_field=new create_field(field,(item->type() == Item::FIELD_ITEM ? ((Item_field *)item)->field : @@ -1376,25 +1382,32 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, DBUG_RETURN(0); extra_fields->push_back(cr_field); } - /* create and lock table */ - /* QQ: This should be done atomic ! */ - if (mysql_create_table(thd, create_table->db, create_table->real_name, - create_info, *extra_fields, *keys, 0, - select_field_count)) - DBUG_RETURN(0); /* + create and lock table + + We don't log the statement, it will be logged later. + If this is a HEAP table, the automatic DELETE FROM which is written to the binlog when a HEAP table is opened for the first time since startup, must not be written: 1) it would be wrong (imagine we're in CREATE SELECT: we don't want to delete from it) 2) it would be written before the CREATE - TABLE, which is a wrong order. So we keep binary logging disabled. + TABLE, which is a wrong order. So we keep binary logging disabled when we + open_table(). + TODO: create and open should be done atomic ! */ - if (!(table= open_table(thd, create_table, &thd->mem_root, (bool*) 0))) + tmp_disable_binlog(thd); + if (!mysql_create_table(thd, create_table->db, create_table->real_name, + create_info, *extra_fields, *keys, 0, + select_field_count)) { - quick_rm_table(create_info->db_type, create_table->db, - table_case_name(create_info, create_table->real_name)); - DBUG_RETURN(0); + if (!(table= open_table(thd, create_table, &thd->mem_root, (bool*) 0))) + quick_rm_table(create_info->db_type, create_table->db, + table_case_name(create_info, create_table->real_name)); } + reenable_binlog(thd); + if (!table) // open failed + DBUG_RETURN(0); + table->reginfo.lock_type=TL_WRITE; if (!((*lock)= mysql_lock_tables(thd, &table,1))) { @@ -1751,7 +1764,7 @@ static int mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(-1); - mysql_ha_close(thd, tables, /*dont_send_ok*/ 1, /*dont_lock*/ 1); + mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL); for (table= tables; table; table= table->next_local) { char table_name[NAME_LEN*2+2]; @@ -2255,31 +2268,32 @@ int mysql_check_table(THD* thd, TABLE_LIST* tables,HA_CHECK_OPT* check_opt) &handler::check)); } + /* table_list should contain just one table */ -int mysql_discard_or_import_tablespace(THD *thd, - TABLE_LIST *table_list, - enum tablespace_op_type tablespace_op) +static int +mysql_discard_or_import_tablespace(THD *thd, + TABLE_LIST *table_list, + enum tablespace_op_type tablespace_op) { TABLE *table; my_bool discard; int error; DBUG_ENTER("mysql_discard_or_import_tablespace"); - /* Note that DISCARD/IMPORT TABLESPACE always is the only operation in an - ALTER TABLE */ + /* + Note that DISCARD/IMPORT TABLESPACE always is the only operation in an + ALTER TABLE + */ thd->proc_info="discard_or_import_tablespace"; - if (tablespace_op == DISCARD_TABLESPACE) - discard = TRUE; - else - discard = FALSE; - - thd->tablespace_op=TRUE; /* we set this flag so that ha_innobase::open - and ::external_lock() do not complain when we - lock the table */ - mysql_ha_close(thd, table_list, /*dont_send_ok*/ 1, /*dont_lock*/ 1); + discard= test(tablespace_op == DISCARD_TABLESPACE); + /* + We set this flag so that ha_innobase::open and ::external_lock() do + not complain when we lock the table + */ + thd->tablespace_op= TRUE; if (!(table=open_ltable(thd,table_list,TL_WRITE))) { thd->tablespace_op=FALSE; @@ -2293,8 +2307,10 @@ int mysql_discard_or_import_tablespace(THD *thd, if (error) goto err; - /* The 0 in the call below means 'not in a transaction', which means - immediate invalidation; that is probably what we wish here */ + /* + The 0 in the call below means 'not in a transaction', which means + immediate invalidation; that is probably what we wish here + */ query_cache_invalidate3(thd, table_list, 0); /* The ALTER TABLE is always in its own transaction */ @@ -2556,8 +2572,7 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, new_db= db; used_fields=create_info->used_fields; - mysql_ha_close(thd, table_list, /*dont_send_ok*/ 1, /*dont_lock*/ 1); - + mysql_ha_flush(thd, table_list, MYSQL_HA_CLOSE_FINAL); /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ if (alter_info->tablespace_op != NO_TABLESPACE_OP) DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, @@ -2600,7 +2615,9 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } else { - if (!access(fn_format(new_name_buff,new_name_buff,new_db,reg_ext,0), + char dir_buff[FN_REFLEN]; + strxnmov(dir_buff, FN_REFLEN, mysql_real_data_home, new_db, NullS); + if (!access(fn_format(new_name_buff,new_name_buff,dir_buff,reg_ext,0), F_OK)) { /* Table will be closed in do_command() */ @@ -3014,14 +3031,15 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } else create_info->data_file_name=create_info->index_file_name=0; - { - /* We don't log the statement, it will be logged later */ - Disable_binlog disable_binlog(thd); - if ((error=mysql_create_table(thd, new_db, tmp_name, - create_info, - create_list,key_list,1,0))) - DBUG_RETURN(error); - } + + /* We don't log the statement, it will be logged later. */ + tmp_disable_binlog(thd); + error= mysql_create_table(thd, new_db, tmp_name, + create_info,create_list,key_list,1,0); + reenable_binlog(thd); + if (error) + DBUG_RETURN(error); + if (table->tmp_table) { TABLE_LIST tbl; @@ -3045,12 +3063,8 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, } - /* - We don't want update TIMESTAMP fields during ALTER TABLE - and copy_data_between_tables uses only write_row() for new_table so - don't need to set up timestamp_on_update_now member. - */ - new_table->timestamp_default_now= 0; + /* We don't want update TIMESTAMP fields during ALTER TABLE. */ + new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields thd->cuted_fields=0L; @@ -3246,8 +3260,8 @@ int mysql_alter_table(THD *thd,char *new_db, char *new_name, my_free((char*) table, MYF(0)); } else - sql_print_error("Warning: Could not open BDB table %s.%s after rename\n", - new_db,table_name); + sql_print_warning("Could not open BDB table %s.%s after rename\n", + new_db,table_name); (void) berkeley_flush_logs(); } #endif @@ -3288,15 +3302,19 @@ copy_data_between_tables(TABLE *from,TABLE *to, List<Item> all_fields; ha_rows examined_rows; bool auto_increment_field_copied= 0; + ulong save_sql_mode; DBUG_ENTER("copy_data_between_tables"); if (!(copy= new Copy_field[to->fields])) DBUG_RETURN(-1); /* purecov: inspected */ - to->file->external_lock(thd,F_WRLCK); + if (to->file->external_lock(thd, F_WRLCK)) + DBUG_RETURN(-1); from->file->info(HA_STATUS_VARIABLE); to->file->start_bulk_insert(from->file->records); + save_sql_mode= thd->variables.sql_mode; + List_iterator<create_field> it(create); create_field *def; copy_end=copy; @@ -3306,7 +3324,17 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (def->field) { if (*ptr == to->next_number_field) + { auto_increment_field_copied= TRUE; + /* + If we are going to copy contents of one auto_increment column to + another auto_increment column it is sensible to preserve zeroes. + This condition also covers case when we are don't actually alter + auto_increment column. + */ + if (def->field == from->found_next_number_field) + thd->variables.sql_mode|= MODE_NO_AUTO_VALUE_ON_ZERO; + } (copy_end++)->set(*ptr,def->field,0); } @@ -3339,7 +3367,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, Turn off recovery logging since rollback of an alter table is to delete the new table so there is no need to log the changes to it. */ - error= ha_recovery_logging(thd,FALSE); + error= ha_enable_transaction(thd,FALSE); if (error) { error= 1; @@ -3402,7 +3430,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, } to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); - ha_recovery_logging(thd,TRUE); + ha_enable_transaction(thd,TRUE); + /* Ensure that the new table is saved properly to disk so that we can do a rename @@ -3411,12 +3440,14 @@ copy_data_between_tables(TABLE *from,TABLE *to, error=1; if (ha_commit(thd)) error=1; - if (to->file->external_lock(thd,F_UNLCK)) - error=1; + err: + thd->variables.sql_mode= save_sql_mode; free_io_cache(from); *copied= found_count; *deleted=delete_count; + if (to->file->external_lock(thd,F_UNLCK)) + error=1; DBUG_RETURN(error > 0 ? -1 : 0); } diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 561f79f9de1..0bb8ac8a28b 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -184,8 +184,7 @@ void udf_init() if (!(dl = dlopen(tmp->dl, RTLD_NOW))) { /* Print warning to log */ - sql_print_error(ER(ER_CANT_OPEN_LIBRARY), - tmp->dl,errno,dlerror()); + sql_print_error(ER(ER_CANT_OPEN_LIBRARY), tmp->dl,errno,dlerror()); /* Keep the udf in the hash so that we can remove it later */ continue; } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index cf4203bf5b8..46f11683e4e 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -153,6 +153,7 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, SELECT_LEX *lex_select_save= thd_arg->lex->current_select; SELECT_LEX *sl, *first_select; select_result *tmp_result; + bool is_union; DBUG_ENTER("st_select_lex_unit::prepare"); describe= test(additional_options & SELECT_DESCRIBE); @@ -189,10 +190,11 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, thd_arg->lex->current_select= sl= first_select= first_select_in_union(); found_rows_for_union= first_select->options & OPTION_FOUND_ROWS; + is_union= test(first_select->next_select()); /* Global option */ - if (first_select->next_select()) + if (is_union) { if (!(tmp_result= union_result= new select_union(0))) goto err; @@ -201,14 +203,11 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, tmp_result= sel_result; } else - { tmp_result= sel_result; - // single select should be processed like select in p[arantses - first_select->braces= 1; - } for (;sl; sl= sl->next_select()) { + bool can_skip_order_by; sl->options|= SELECT_NO_UNLOCK; JOIN *join= new JOIN(thd_arg, sl->item_list, sl->options | thd_arg->options | additional_options, @@ -220,18 +219,26 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, set_limit(sl, sl); if (sl->braces) sl->options&= ~OPTION_FOUND_ROWS; + + can_skip_order_by= is_union && + (!sl->braces || select_limit_cnt == HA_POS_ERROR); res= join->prepare(&sl->ref_pointer_array, (TABLE_LIST*) sl->table_list.first, sl->with_wild, sl->where, - ((sl->braces) ? sl->order_list.elements : 0) + - sl->group_list.elements, - (sl->braces) ? - (ORDER *)sl->order_list.first : (ORDER *) 0, + (can_skip_order_by ? 0 : sl->order_list.elements) + + sl->group_list.elements, + can_skip_order_by ? + (ORDER*) 0 : (ORDER *)sl->order_list.first, (ORDER*) sl->group_list.first, sl->having, - (ORDER*) NULL, + (is_union ? (ORDER*) 0 : + (ORDER*) thd_arg->lex->proc_list.first), sl, this); + /* There are no * in the statement anymore (for PS) */ + sl->with_wild= 0; + last_procedure= join->procedure; + if (res || thd_arg->is_fatal_error) goto err; if (sl == first_select) @@ -267,9 +274,26 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, } } - // it is not single select - if (first_select->next_select()) + if (is_union) { + /* + Check that it was possible to aggregate + all collations together for UNION. + */ + List_iterator_fast<Item> tp(types); + Item_arena *arena= thd->current_arena; + Item *type; + + while ((type= tp++)) + { + if (type->result_type() == STRING_RESULT && + type->collation.derivation == DERIVATION_NONE) + { + my_error(ER_CANT_AGGREGATE_NCOLLATIONS, MYF(0), "UNION"); + goto err; + } + } + union_result->tmp_table_param.field_count= types.elements; if (!(table= create_tmp_table(thd_arg, &union_result->tmp_table_param, types, @@ -291,7 +315,6 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, if (!item_list.elements) { Field **field; - Item_arena *arena= thd->current_arena; Item_arena backup; if (arena->is_conventional()) arena= 0; @@ -334,14 +357,27 @@ int st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, 0, 0, fake_select_lex->order_list.elements, (ORDER*) fake_select_lex->order_list.first, - (ORDER*) NULL, NULL, (ORDER*) NULL, + (ORDER*) NULL, NULL, + (ORDER*) NULL, fake_select_lex, this); fake_select_lex->table_list.empty(); } } + else if (!arena->is_conventional()) + { + /* + We're in execution of a prepared statement or stored procedure: + reset field items to point at fields from the created temporary table. + */ + List_iterator_fast<Item> it(item_list); + for (Field **field= table->field; *field; field++) + { + Item_field *item_field= (Item_field*) it++; + DBUG_ASSERT(item_field); + item_field->reset_field(*field); + } + } } - else - first_select->braces= 0; // remove our changes thd_arg->lex->current_select= lex_select_save; @@ -358,6 +394,7 @@ int st_select_lex_unit::exec() SELECT_LEX *lex_select_save= thd->lex->current_select; SELECT_LEX *select_cursor=first_select_in_union(); ulonglong add_rows=0; + ha_rows examined_rows= 0; DBUG_ENTER("st_select_lex_unit::exec"); if (executed && !uncacheable && !describe) @@ -438,6 +475,7 @@ int st_select_lex_unit::exec() offset_limit_cnt= sl->offset_limit; if (!res && union_result->flush()) { + examined_rows+= thd->examined_row_count; thd->lex->current_select= lex_select_save; DBUG_RETURN(1); } @@ -516,7 +554,10 @@ int st_select_lex_unit::exec() fake_select_lex->table_list.empty(); if (!res) + { thd->limit_found_rows = (ulonglong)table->file->records + add_rows; + thd->examined_row_count+= examined_rows; + } /* Mark for slow query log if any of the union parts didn't use indexes efficiently diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 00e70ccb484..4d64455ab53 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -171,7 +171,7 @@ int mysql_update(THD *thd, { // Don't set timestamp column if this is modified if (table->timestamp_field->query_id == thd->query_id) - table->timestamp_on_update_now= 0; + table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; else table->timestamp_field->query_id=timestamp_query_id; } @@ -392,6 +392,7 @@ int mysql_update(THD *thd, else if (handle_duplicates != DUP_IGNORE || error != HA_ERR_FOUND_DUPP_KEY) { + thd->fatal_error(); // Force error message table->file->print_error(error,MYF(0)); error= 1; break; @@ -544,9 +545,22 @@ int mysql_prepare_update(THD *thd, TABLE_LIST *table_list, ***************************************************************************/ /* - Setup multi-update handling and call SELECT to do the join + Get table map for list of Item_field */ +static table_map get_table_map(List<Item> *items) +{ + List_iterator_fast<Item> item_it(*items); + Item_field *item; + table_map map= 0; + + while ((item= (Item_field *) item_it++)) + map|= item->used_tables(); + DBUG_PRINT("info",("table_map: 0x%08x", map)); + return map; +} + + /* make update specific preparation and checks after opening tables @@ -658,6 +672,10 @@ int mysql_multi_update_prepare(THD *thd) } +/* + Setup multi-update handling and call SELECT to do the join +*/ + int mysql_multi_update(THD *thd, TABLE_LIST *table_list, List<Item> *fields, @@ -719,7 +737,7 @@ int multi_update::prepare(List<Item> ¬_used_values, { TABLE_LIST *table_ref; SQL_LIST update; - table_map tables_to_update= 0; + table_map tables_to_update; Item_field *item; List_iterator_fast<Item> field_it(*fields); List_iterator_fast<Item> value_it(*values); @@ -730,8 +748,7 @@ int multi_update::prepare(List<Item> ¬_used_values, thd->cuted_fields=0L; thd->proc_info="updating main table"; - while ((item= (Item_field *) field_it++)) - tables_to_update|= item->used_tables(); + tables_to_update= get_table_map(fields); if (!tables_to_update) { @@ -794,7 +811,6 @@ int multi_update::prepare(List<Item> ¬_used_values, /* Split fields into fields_for_table[] and values_by_table[] */ - field_it.rewind(); while ((item= (Item_field *) field_it++)) { Item *value= value_it++; @@ -1049,11 +1065,16 @@ bool multi_update::send_data(List<Item> ¬_used_values) if ((error=table->file->update_row(table->record[1], table->record[0]))) { - table->file->print_error(error,MYF(0)); updated--; - DBUG_RETURN(1); + if (handle_duplicates != DUP_IGNORE || + error != HA_ERR_FOUND_DUPP_KEY) + { + thd->fatal_error(); // Force error message + table->file->print_error(error,MYF(0)); + DBUG_RETURN(1); + } } - if (!table->file->has_transactions()) + else if (!table->file->has_transactions()) thd->no_trans_update= 1; } } @@ -1119,7 +1140,6 @@ int multi_update::do_updates(bool from_send_error) TABLE *table, *tmp_table; DBUG_ENTER("do_updates"); - do_update= 0; // Don't retry this function if (!found) DBUG_RETURN(0); @@ -1207,7 +1227,10 @@ int multi_update::do_updates(bool from_send_error) err: if (!from_send_error) + { + thd->fatal_error(); table->file->print_error(local_error,MYF(0)); + } (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d792d144545..bdda1f6fa07 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -917,14 +917,14 @@ prepare_src: THD *thd=YYTHD; LEX *lex= thd->lex; lex->prepared_stmt_code= $1; - lex->prepared_stmt_code_is_varref= false; + lex->prepared_stmt_code_is_varref= FALSE; } | '@' ident_or_text { THD *thd=YYTHD; LEX *lex= thd->lex; lex->prepared_stmt_code= $2; - lex->prepared_stmt_code_is_varref= true; + lex->prepared_stmt_code_is_varref= TRUE; }; execute: @@ -2388,20 +2388,20 @@ trg_event: ; create2: - '(' create2a {} - | opt_create_table_options create3 {} - | LIKE table_ident - { - LEX *lex=Lex; - if (!(lex->name= (char *)$2)) + '(' create2a {} + | opt_create_table_options create3 {} + | LIKE table_ident + { + LEX *lex=Lex; + if (!(lex->name= (char *)$2)) YYABORT; - } - | '(' LIKE table_ident ')' - { - LEX *lex=Lex; - if (!(lex->name= (char *)$3)) + } + | '(' LIKE table_ident ')' + { + LEX *lex=Lex; + if (!(lex->name= (char *)$3)) YYABORT; - } + } ; create2a: @@ -2739,10 +2739,21 @@ type: if (YYTHD->variables.sql_mode & MODE_MAXDB) $$=FIELD_TYPE_DATETIME; else + { + /* + Unlike other types TIMESTAMP fields are NOT NULL by default. + */ + Lex->type|= NOT_NULL_FLAG; $$=FIELD_TYPE_TIMESTAMP; + } } - | TIMESTAMP '(' NUM ')' { Lex->length=$3.str; - $$=FIELD_TYPE_TIMESTAMP; } + | TIMESTAMP '(' NUM ')' + { + LEX *lex= Lex; + lex->length= $3.str; + lex->type|= NOT_NULL_FLAG; + $$= FIELD_TYPE_TIMESTAMP; + } | DATETIME { $$=FIELD_TYPE_DATETIME; } | TINYBLOB { Lex->charset=&my_charset_bin; $$=FIELD_TYPE_TINY_BLOB; } @@ -3130,7 +3141,17 @@ key_list: key_part: ident { $$=new key_part_spec($1.str); } - | ident '(' NUM ')' { $$=new key_part_spec($1.str,(uint) atoi($3.str)); }; + | ident '(' NUM ')' + { + int key_part_len= atoi($3.str); + if (!key_part_len) + { + my_printf_error(ER_UNKNOWN_ERROR, + "Key part '%s' length cannot be 0", + MYF(0), $1.str); + } + $$=new key_part_spec($1.str,(uint) key_part_len); + }; opt_ident: /* empty */ { $$=(char*) 0; } /* Defaultlength */ @@ -4242,7 +4263,7 @@ simple_expr: | CONCAT '(' expr_list ')' { $$= new Item_func_concat(* $3); } | CONCAT_WS '(' expr ',' expr_list ')' - { $$= new Item_func_concat_ws($3, *$5); } + { $5->push_front($3); $$= new Item_func_concat_ws(*$5); } | CONTAINS_SYM '(' expr ',' expr ')' { $$= create_func_contains($3, $5); } | CONVERT_TZ_SYM '(' expr ',' expr ',' expr ')' @@ -5395,15 +5416,11 @@ select_var_ident: into: INTO OUTFILE TEXT_STRING_sys { - LEX *lex=Lex; - if (!lex->describe) - { - lex->uncacheable(UNCACHEABLE_SIDEEFFECT); - if (!(lex->exchange= new sql_exchange($3.str,0))) - YYABORT; - if (!(lex->result= new select_export(lex->exchange))) - YYABORT; - } + LEX *lex= Lex; + lex->uncacheable(UNCACHEABLE_SIDEEFFECT); + if (!(lex->exchange= new sql_exchange($3.str, 0)) || + !(lex->result= new select_export(lex->exchange))) + YYABORT; } opt_field_term opt_line_term | INTO DUMPFILE TEXT_STRING_sys @@ -5702,14 +5719,18 @@ expr_or_default: opt_insert_update: /* empty */ | ON DUPLICATE_SYM - { /* for simplisity, let's forget about - INSERT ... SELECT ... UPDATE - for a moment */ - if (Lex->sql_command != SQLCOM_INSERT) + { + LEX *lex= Lex; + /* + For simplicity, let's forget about INSERT ... SELECT ... UPDATE + for a moment. + */ + if (lex->sql_command != SQLCOM_INSERT) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } + lex->duplicates= DUP_UPDATE; } KEY_SYM UPDATE_SYM update_list ; @@ -5725,12 +5746,14 @@ update: lex->lock_option= TL_UNLOCK; /* Will be set later */ } opt_low_priority opt_ignore join_table_list - SET update_list where_clause opt_order_clause delete_limit_clause + SET update_list { LEX *lex= Lex; - Select->set_lock_for_tables($3); if (lex->select_lex.table_list.elements > 1) + { lex->sql_command= SQLCOM_UPDATE_MULTI; + lex->multi_lock_option= $3; + } else if (lex->select_lex.get_table_list()->derived) { /* it is single table update and it is update of derived table */ @@ -5738,7 +5761,10 @@ update: lex->select_lex.get_table_list()->alias, "UPDATE"); YYABORT; } + else + Select->set_lock_for_tables($3); } + where_clause opt_order_clause delete_limit_clause {} ; update_list: @@ -6319,15 +6345,28 @@ field_term_list: | field_term; field_term: - TERMINATED BY text_string { Lex->exchange->field_term= $3;} + TERMINATED BY text_string + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->field_term= $3; + } | OPTIONALLY ENCLOSED BY text_string { - LEX *lex=Lex; - lex->exchange->enclosed= $4; - lex->exchange->opt_enclosed=1; + LEX *lex= Lex; + DBUG_ASSERT(lex->exchange); + lex->exchange->enclosed= $4; + lex->exchange->opt_enclosed= 1; } - | ENCLOSED BY text_string { Lex->exchange->enclosed= $3;} - | ESCAPED BY text_string { Lex->exchange->escaped= $3;}; + | ENCLOSED BY text_string + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->enclosed= $3; + } + | ESCAPED BY text_string + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->escaped= $3; + }; opt_line_term: /* empty */ @@ -6338,13 +6377,24 @@ line_term_list: | line_term; line_term: - TERMINATED BY text_string { Lex->exchange->line_term= $3;} - | STARTING BY text_string { Lex->exchange->line_start= $3;}; + TERMINATED BY text_string + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->line_term= $3; + } + | STARTING BY text_string + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->line_start= $3; + }; opt_ignore_lines: /* empty */ - | IGNORE_SYM NUM LINES - { Lex->exchange->skip_lines=atol($2.str); }; + | IGNORE_SYM NUM LINES + { + DBUG_ASSERT(Lex->exchange); + Lex->exchange->skip_lines= atol($2.str); + }; /* Common definitions */ @@ -6505,7 +6555,7 @@ simple_ident: $$= (sel->parsing_place != IN_HAVING || sel->get_in_sum_expr() > 0) ? (Item*) new Item_field(NullS,NullS,$1.str) : - (Item*) new Item_ref(0,0, NullS,NullS,$1.str); + (Item*) new Item_ref(NullS,NullS,$1.str); } } | simple_ident_q { $$= $1; } @@ -6518,7 +6568,7 @@ simple_ident_nospvar: $$= (sel->parsing_place != IN_HAVING || sel->get_in_sum_expr() > 0) ? (Item*) new Item_field(NullS,NullS,$1.str) : - (Item*) new Item_ref(0,0,NullS,NullS,$1.str); + (Item*) new Item_ref(NullS,NullS,$1.str); } | simple_ident_q { $$= $1; } ; @@ -6588,7 +6638,7 @@ simple_ident_q: $$= (sel->parsing_place != IN_HAVING || sel->get_in_sum_expr() > 0) ? (Item*) new Item_field(NullS,$1.str,$3.str) : - (Item*) new Item_ref(0,0,NullS,$1.str,$3.str); + (Item*) new Item_ref(NullS,$1.str,$3.str); } } | '.' ident '.' ident @@ -6605,7 +6655,7 @@ simple_ident_q: $$= (sel->parsing_place != IN_HAVING || sel->get_in_sum_expr() > 0) ? (Item*) new Item_field(NullS,$2.str,$4.str) : - (Item*) new Item_ref(0,0,NullS,$2.str,$4.str); + (Item*) new Item_ref(NullS, $2.str, $4.str); } | ident '.' ident '.' ident { @@ -6623,8 +6673,8 @@ simple_ident_q: (Item*) new Item_field((YYTHD->client_capabilities & CLIENT_NO_SCHEMA ? NullS : $1.str), $3.str, $5.str) : - (Item*) new Item_ref(0,0,(YYTHD->client_capabilities & - CLIENT_NO_SCHEMA ? NullS : $1.str), + (Item*) new Item_ref((YYTHD->client_capabilities & + CLIENT_NO_SCHEMA ? NullS : $1.str), $3.str, $5.str); }; @@ -6958,6 +7008,7 @@ keyword: | TIME_SYM {} | TYPE_SYM {} | TYPES_SYM {} + | UDF_RETURNS_SYM {} | FUNCTION_SYM {} | UNCOMMITTED_SYM {} | UNDEFINED_SYM {} diff --git a/sql/stacktrace.c b/sql/stacktrace.c index d0478052fb1..322d647e741 100644 --- a/sql/stacktrace.c +++ b/sql/stacktrace.c @@ -197,7 +197,7 @@ terribly wrong...\n"); fprintf(stderr, "Stack trace seems successful - bottom reached\n"); end: - fprintf(stderr, "Please read http://www.mysql.com/doc/en/Using_stack_trace.html and follow instructions on how to resolve the stack trace. Resolved\n\ + fprintf(stderr, "Please read http://dev.mysql.com/doc/mysql/en/Using_stack_trace.html and follow instructions on how to resolve the stack trace. Resolved\n\ stack trace is much more helpful in diagnosing the problem, so please do \n\ resolve it\n"); } diff --git a/sql/strfunc.cc b/sql/strfunc.cc index d00e57df5a1..b5255e9be06 100644 --- a/sql/strfunc.cc +++ b/sql/strfunc.cc @@ -39,16 +39,13 @@ static const char field_separator=','; -ulonglong find_set(TYPELIB *lib, const char *str, uint length, char **err_pos, - uint *err_len, bool *set_warning) +ulonglong find_set(TYPELIB *lib, const char *str, uint length, CHARSET_INFO *cs, + char **err_pos, uint *err_len, bool *set_warning) { - const char *end= str + length; - *err_pos= 0; // No error yet - while (end > str && my_isspace(system_charset_info, end[-1])) - end--; - - *err_len= 0; + CHARSET_INFO *strip= cs ? cs : &my_charset_latin1; + const char *end= str + strip->cset->lengthsp(strip, str, length); ulonglong found= 0; + *err_pos= 0; // No error yet if (str != end) { const char *start= str; @@ -59,7 +56,8 @@ ulonglong find_set(TYPELIB *lib, const char *str, uint length, char **err_pos, for (; pos != end && *pos != field_separator; pos++) ; var_len= (uint) (pos - start); - uint find= find_type(lib, start, var_len, 0); + uint find= cs ? find_type2(lib, start, var_len, cs) : + find_type(lib, start, var_len, (bool) 0); if (!find) { *err_pos= (char*) start; @@ -117,6 +115,47 @@ uint find_type(TYPELIB *lib, const char *find, uint length, bool part_match) /* + Find a string in a list of strings according to collation + + SYNOPSIS + find_type2() + lib TYPELIB (struct of pointer to values + count) + x String to find + length String length + cs Character set + collation to use for comparison + + NOTES + + RETURN + 0 No matching value + >0 Offset+1 in typelib for matched string +*/ + +uint find_type2(TYPELIB *typelib, const char *x, uint length, CHARSET_INFO *cs) +{ + int find,pos,findpos; + const char *j; + DBUG_ENTER("find_type2"); + DBUG_PRINT("enter",("x: '%s' lib: 0x%lx",x,typelib)); + + if (!typelib->count) + { + DBUG_PRINT("exit",("no count")); + DBUG_RETURN(0); + } + LINT_INIT(findpos); + for (find=0, pos=0 ; (j=typelib->type_names[pos]) ; pos++) + { + if (!my_strnncoll(cs, (const uchar*) x, length, + (const uchar*) j, typelib->type_lengths[pos])) + DBUG_RETURN(pos+1); + } + DBUG_PRINT("exit",("Couldn't find type")); + DBUG_RETURN(0); +} /* find_type */ + + +/* Check if the first word in a string is one of the ones in TYPELIB SYNOPSIS diff --git a/sql/table.cc b/sql/table.cc index e372de57177..0116cf180c1 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -379,6 +379,24 @@ int openfrm(THD *thd, const char *name, const char *alias, uint db_stat, fix_type_pointers(&int_array,&outparam->fieldnames,1,&names); fix_type_pointers(&int_array,outparam->intervals,interval_count, &names); + + { + /* Set ENUM and SET lengths */ + TYPELIB *interval; + for (interval= outparam->intervals; + interval < outparam->intervals + interval_count; + interval++) + { + uint count= (uint) (interval->count + 1) * sizeof(uint); + if (!(interval->type_lengths= (uint *) alloc_root(&outparam->mem_root, + count))) + goto err_not_open; + for (count= 0; count < interval->count; count++) + interval->type_lengths[count]= strlen(interval->type_names[count]); + interval->type_lengths[count]= 0; + } + } + if (keynames) fix_type_pointers(&int_array,&outparam->keynames,1,&keynames); VOID(my_close(file,MYF(MY_WME))); @@ -731,6 +749,14 @@ int openfrm(THD *thd, const char *name, const char *alias, uint db_stat, outparam->crashed=((err == HA_ERR_CRASHED_ON_USAGE) && outparam->file->auto_repair() && !(ha_open_flags & HA_OPEN_FOR_REPAIR)); + + if (err==HA_ERR_NO_SUCH_TABLE) + { + /* The table did not exists in storage engine, use same error message + as if the .frm file didn't exist */ + error= 1; + my_errno= ENOENT; + } goto err_not_open; /* purecov: inspected */ } } @@ -1030,14 +1056,19 @@ TYPELIB *typelib(List<String> &strings) return 0; result->count=strings.elements; result->name=""; - if (!(result->type_names=(const char **) sql_alloc(sizeof(char *)* - (result->count+1)))) + uint nbytes= (sizeof(char*) + sizeof(uint)) * (result->count + 1); + if (!(result->type_names= (const char**) sql_alloc(nbytes))) return 0; + result->type_lengths= (uint*) (result->type_names + result->count + 1); List_iterator<String> it(strings); String *tmp; for (uint i=0; (tmp=it++) ; i++) - result->type_names[i]=tmp->ptr(); - result->type_names[result->count]=0; // End marker + { + result->type_names[i]= tmp->ptr(); + result->type_lengths[i]= tmp->length(); + } + result->type_names[result->count]= 0; // End marker + result->type_lengths[result->count]= 0; return result; } diff --git a/sql/table.h b/sql/table.h index 3e2a61d5a21..d5bbd0ac2e2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -70,6 +70,16 @@ typedef struct st_filesort_info } FILESORT_INFO; +/* + Values in this enum are used to indicate during which operations value + of TIMESTAMP field should be set to current timestamp. +*/ +enum timestamp_auto_set_type +{ + TIMESTAMP_NO_AUTO_SET= 0, TIMESTAMP_AUTO_SET_ON_INSERT= 1, + TIMESTAMP_AUTO_SET_ON_UPDATE= 2, TIMESTAMP_AUTO_SET_ON_BOTH= 3 +}; + /* Table cache entry struct */ class Field_timestamp; @@ -113,16 +123,19 @@ struct st_table { uint status; /* Used by postfix.. */ uint system; /* Set if system record */ - /* - These two members hold offset in record + 1 for TIMESTAMP field - with NOW() as default value or/and with ON UPDATE NOW() option. - If 0 then such field is absent in this table or auto-set for default - or/and on update should be temporally disabled for some reason. - These values is setup to offset value for each statement in open_table() - and turned off in statement processing code (see mysql_update as example). + /* + If this table has TIMESTAMP field with auto-set property (pointed by + timestamp_field member) then this variable indicates during which + operations (insert only/on update/in both cases) we should set this + field to current timestamp. If there are no such field in this table + or we should not automatically set its value during execution of current + statement then the variable contains TIMESTAMP_NO_AUTO_SET (i.e. 0). + + Value of this variable is set for each statement in open_table() and + if needed cleared later in statement processing code (see mysql_update() + as example). */ - ulong timestamp_default_now; - ulong timestamp_on_update_now; + timestamp_auto_set_type timestamp_field_type; /* Index of auto-updated TIMESTAMP field in field array */ uint timestamp_field_offset; @@ -151,6 +164,14 @@ struct st_table { *found_next_number_field, /* Set on open */ *rowid_field; Field_timestamp *timestamp_field; +#if MYSQL_VERSION_ID < 40100 + /* + Indicates whenever we have to set field_length members of all TIMESTAMP + fields to 19 (to honour 'new_mode' variable) or to original + field_length values. + */ + my_bool timestamp_mode; +#endif my_string comment; /* Comment about table */ CHARSET_INFO *table_charset; /* Default charset of string fields */ REGINFO reginfo; /* field connections */ @@ -159,6 +180,7 @@ struct st_table { /* Table's triggers, 0 if there are no of them */ Table_triggers_list *triggers; + /* A pair "database_name\0table_name\0", widely used as simply a db name */ char *table_cache_key; char *table_name,*real_name,*path; uint key_length; /* Length of key */ diff --git a/sql/time.cc b/sql/time.cc index f47d7652864..562f9956ccc 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -746,13 +746,7 @@ const char *get_date_time_format_str(KNOWN_DATE_TIME_FORMAT *format, void make_time(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%s%02d:%02d:%02d", - (l_time->neg ? "-" : ""), - l_time->hour, - l_time->minute, - l_time->second)); + uint length= (uint) my_time_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -761,12 +755,7 @@ void make_time(const DATE_TIME_FORMAT *format __attribute__((unused)), void make_date(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%04d-%02d-%02d", - l_time->year, - l_time->month, - l_time->day)); + uint length= (uint) my_date_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -775,15 +764,7 @@ void make_date(const DATE_TIME_FORMAT *format __attribute__((unused)), void make_datetime(const DATE_TIME_FORMAT *format __attribute__((unused)), const TIME *l_time, String *str) { - long length= my_sprintf((char*) str->ptr(), - ((char*) str->ptr(), - "%04d-%02d-%02d %02d:%02d:%02d", - l_time->year, - l_time->month, - l_time->day, - l_time->hour, - l_time->minute, - l_time->second)); + uint length= (uint) my_datetime_to_str(l_time, (char*) str->ptr()); str->length(length); str->set_charset(&my_charset_bin); } @@ -899,38 +880,4 @@ ulonglong TIME_to_ulonglong(const TIME *time) return 0; } - -/* - Convert struct DATE/TIME/DATETIME value to string using built-in - MySQL time conversion formats. - - SYNOPSIS - TIME_to_string() - - NOTE - The string must have at least MAX_DATE_REP_LENGTH bytes reserved. -*/ - -void TIME_to_string(const TIME *time, String *str) -{ - switch (time->time_type) { - case MYSQL_TIMESTAMP_DATETIME: - make_datetime((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_DATE: - make_date((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_TIME: - make_time((DATE_TIME_FORMAT*) 0, time, str); - break; - case MYSQL_TIMESTAMP_NONE: - case MYSQL_TIMESTAMP_ERROR: - str->length(0); - str->set_charset(&my_charset_bin); - break; - default: - DBUG_ASSERT(0); - } -} - #endif diff --git a/sql/tztime.cc b/sql/tztime.cc index 9fd0694ade4..50c496577e0 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1456,6 +1456,10 @@ tz_init_table_list(TABLE_LIST *tz_tabs, TABLE_LIST ***global_next_ptr) link this list to the end of global table list (it will read and update accordingly variable pointed by global_next_ptr for this). + NOTE + my_tz_check_n_skip_implicit_tables() function depends on fact that + elements of list created are allocated as TABLE_LIST[4] array. + RETURN VALUES Returns pointer to first TABLE_LIST object, (could be 0 if time zone tables don't exist) and &fake_time_zone_tables_list in case of error. @@ -1662,8 +1666,8 @@ end_with_setting_default_tz: /* If we have default time zone try to load it */ if (default_tzname) { - String tzname(default_tzname, &my_charset_latin1); - if (!(global_system_variables.time_zone= my_tz_find(&tzname, tables))) + String tmp_tzname(default_tzname, &my_charset_latin1); + if (!(global_system_variables.time_zone= my_tz_find(&tmp_tzname, tables))) { sql_print_error("Fatal error: Illegal or unknown default time zone '%s'", default_tzname); diff --git a/sql/tztime.h b/sql/tztime.h index 1325e921c67..6d2388bb160 100644 --- a/sql/tztime.h +++ b/sql/tztime.h @@ -64,6 +64,35 @@ extern Time_zone * my_tz_find(const String *name, TABLE_LIST *tz_tables); extern my_bool my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap); extern void my_tz_free(); + +/* + Check if we have pointer to the beggining of list of implictly used + time zone tables and fast-forward to its end. + + SYNOPSIS + my_tz_check_n_skip_implicit_tables() + table - (in/out) pointer to element of table list to check + tz_tables - list of implicitly used time zone tables received + from my_tz_get_table_list() function. + + NOTE + This function relies on my_tz_get_table_list() implementation. + + RETURN VALUE + TRUE - if table points to the beggining of tz_tables list + FALSE - otherwise. +*/ +inline bool my_tz_check_n_skip_implicit_tables(TABLE_LIST **table, + TABLE_LIST *tz_tables) +{ + if (*table == tz_tables) + { + (*table)+= 3; + return TRUE; + } + return FALSE; +} + /* Maximum length of time zone name that we support (Time zone name is char(64) in db) |