diff options
-rw-r--r-- | Docs/Makefile.am | 3 | ||||
-rw-r--r-- | Docs/manual.texi | 129 | ||||
-rw-r--r-- | include/mysql_com.h | 1 | ||||
-rw-r--r-- | myisam/ft_update.c | 9 | ||||
-rw-r--r-- | mysql-test/mysql-test-run.sh | 5 | ||||
-rw-r--r-- | mysql-test/r/func_crypt.result | 3 | ||||
-rw-r--r-- | mysql-test/r/func_encrypt.result | bin | 6980 -> 8764 bytes | |||
-rw-r--r-- | mysql-test/r/func_str.result | 3 | ||||
-rw-r--r-- | mysql-test/std_data/des_key_file | 4 | ||||
-rw-r--r-- | mysql-test/t/func_crypt.test | 3 | ||||
-rw-r--r-- | mysql-test/t/func_encrypt-master.opt | 1 | ||||
-rw-r--r-- | mysql-test/t/func_encrypt.test | 105 | ||||
-rw-r--r-- | mysql-test/t/func_str.test | 2 | ||||
-rw-r--r-- | sql/des_key_file.cc | 45 | ||||
-rw-r--r-- | sql/item_create.cc | 3 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 150 | ||||
-rw-r--r-- | sql/item_strfunc.h | 13 | ||||
-rw-r--r-- | sql/item_timefunc.cc | 2 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/mysql_priv.h | 6 | ||||
-rw-r--r-- | sql/mysqld.cc | 2 | ||||
-rw-r--r-- | sql/sql_parse.cc | 28 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 58 |
23 files changed, 402 insertions, 174 deletions
diff --git a/Docs/Makefile.am b/Docs/Makefile.am index eca667cf7ef..f5a589fdf78 100644 --- a/Docs/Makefile.am +++ b/Docs/Makefile.am @@ -21,7 +21,8 @@ info_TEXINFOS = manual.texi targets = manual.txt mysql.info manual.html BUILT_SOURCES = $(targets) manual_toc.html include.texi -EXTRA_DIST = $(noinst_SCRIPTS) $(BUILT_SOURCES) mysqld_error.txt INSTALL-BINARY +EXTRA_DIST = $(noinst_SCRIPTS) $(BUILT_SOURCES) mysqld_error.txt \ + INSTALL-BINARY mirrors.texi section.Comparisons.texi all: $(targets) txt_files diff --git a/Docs/manual.texi b/Docs/manual.texi index 549db7ef1e1..a99ad67a233 100644 --- a/Docs/manual.texi +++ b/Docs/manual.texi @@ -7304,8 +7304,11 @@ version 4.0; @code{LOCATE()} and @code{INSTR()} are case sensitive if neither argument is a binary string. @item -In 3.23, -@code{INSERT INTO ... SELECT} always had @code{IGNORE} enabled. +@code{HEX(string)} now returns the characters in string converted to +hexadecimal. If you want to convert a number to hexadecimal, you should +ensure that you call @code{HEX()} with a numeric argument. +@item +In 3.23, @code{INSERT INTO ... SELECT} always had @code{IGNORE} enabled. In 4.0.1, MySQL will stop (and possibly roll back) in case of an error if you don't specify @code{IGNORE}. @item @@ -13134,21 +13137,25 @@ specify @code{--core-file-size} to @code{safe_mysqld}. @xref{safe_mysqld, , @item -h, --datadir=path Path to the database root. +@item --debug[...]= +If MySQL is configured with @code{--with-debug}, you can use this +option to get a trace file of what @code{mysqld} is doing. +@xref{Making trace files}. + @item --default-character-set=charset Set the default character set. @xref{Character sets}. @item --default-table-type=type Set the default table type for tables. @xref{Table types}. -@item --debug[...]= -If MySQL is configured with @code{--with-debug}, you can use this -option to get a trace file of what @code{mysqld} is doing. -@xref{Making trace files}. - @item --delay-key-write-for-all-tables Don't flush key buffers between writes for any @code{MyISAM} table. @xref{Server parameters}. +@item --des-key-file=filename +Read the default keys used by @code{des_encrypt()} and @code{des_decrypt()} +from this file. + @item --enable-locking Enable system locking. Note that if you use this option on a system which a not fully working lockd() (as on Linux) you will easily get @@ -17911,6 +17918,8 @@ Flushing the host tables allows the host to attempt to connect again. @xref{Blocked host}. You can start @code{mysqld} with @code{-O max_connection_errors=999999999} to avoid this error message. +@item @code{DES_KEY_FILE} @tab Reloads the des keys from the file specified with @code{--des-key-file}. + @item @code{LOGS} @tab Closes and reopens all log files. If you have specified the update log file or a binary log file without an extension, the extension number of the log file will be incremented @@ -26644,7 +26653,8 @@ mysql> select 0x5061756c; The x'hexstring' syntax (new in 4.0) is based on ANSI SQL and the 0x syntax is based on ODBC. Hexadecimal strings are often used by ODBC to give values for BLOB columns. - +You can convert a string or a number to hexadecimal with the @code{HEX()} +function. @node NULL values, , Hexadecimal values, Literals @subsubsection @code{NULL} Values @@ -29173,14 +29183,23 @@ mysql> select OCT(12); @end example @findex HEX() -@item HEX(N) -Returns a string representation of the hexadecimal value of @code{N}, where -@code{N} is a longlong (@code{BIGINT}) number. This is equivalent to -@code{CONV(N,10,16)}. Returns @code{NULL} if @code{N} is @code{NULL}: +@item HEX(N_or_S) + +If N_OR_S is a number, returns a string representation of the hexadecimal +value of @code{N}, where @code{N} is a longlong (@code{BIGINT}) number. +This is equivalent to @code{CONV(N,10,16)}. + +If N_OR_S is a string, returns a hexadecimal string of N_OR_S where each +character in N_OR_S is converted to 2 hexadecimal digits. This is the +invers of the @code{0xff} strings. @example mysql> select HEX(255); -> 'FF' +mysql> select HEX("abc"); + -> 616263 +mysql> select 0x616263; + -> "abc" @end example @findex CHAR() @@ -31041,6 +31060,83 @@ mysql> select MD5("testing"); This is an "RSA Data Security, Inc. MD5 Message-Digest Algorithm". +@findex des_encrypt() +@item des_encrypt(string_to_encrypt, flag, [, (key_number | key_string) ] ) + +Encrypts the string with the given key using the DES algorithm, which +provides strong encryption. + +Note that this function only works if you have configured MySQL with +SLL support. @xref{Secure connections}. + +The encryption key to use is chosen the following way: + +@multitable @columnfractions .2 .8 +@item @strong{Argument} @tab @strong{Description} +@item Only one argument @tab +The first key from @code{des-key-file} is used. +@item key number @tab +The given key (0-9) from the @code{des-key-file} is used. +@item string @tab +The given @code{key_string} will be used to crypt @code{string_to_encrypt}. +@end multitable + +The return string will be a binary string where the first character +will be @code{CHAR(128 | key-number)}. + +The 128 is added to make it easier to recognize a crypted key. +If one uses a string key, @code{key-number} will be 127. + +On error, this function returns NULL. + +The string length for the result will be +@code{new_length= org_length + (8-(org_length % 8))+1}. + +The @code{des-key-file} has the following format: + +@example +key-number key-string +key-number key-string +@end example + +The @code{key-number} must be a number between 0-9. The numbers may be +in any order. @code{des-key-string} is string that will be used to +crypt the message. Between the number and the key there should be at +least one space. The first key is the default key that will be used +if one doesn't specify a key to @code{des_encrypt()} + +You can tell MySQL to read new key values from the key file with the +@code{FLUSH DES_KEY_FILE} command. + +One benefit with having a set of default keys on can use is that it +gives applications a way to check for existence of crypted column, +without giving the end user the right to uncrypt the data. + +@example +SELECT customer_address FROM customer_table WHERE +crypted_credit_card = DES_ENCRYPT("credit_card_number"); +@end example + +@findex des_decrypt() +@item des_decrypt(string_to_decrypt [, key_string]) + +Decrypts a string crypted with @code{des_encrypt()}. + +Note that this function only works if you have configured MySQL with +SLL support. @xref{Secure connections}. + +If one only gives this a string argument, then it will use the right key +from the @code{des-key-file} to decrypt the message. For this to work +the user must have the @code{PROCESS_PRIV} privilege. + +If one calls this function with 2 arguments, the second argument is +used to decrypt the message. + +If the @code{string_to_decrypt} doesn't look like a crypted string MySQL will +return the given @code{string_to_decrypt}. + +On error, this function returns NULL. + @findex LAST_INSERT_ID([expr]) @item LAST_INSERT_ID([expr]) Returns the last automatically generated value that was inserted into an @@ -46836,6 +46932,15 @@ Our TODO section contains what we plan to have in 4.0. @xref{TODO MySQL 4.0}. @itemize @bullet @item +Added functions @code{des_encrypt()} and @code{des_decrypt()}. +@item +Added statement FLUSH DES_KEY_FILE. +@item +Added mysqld option @code{--des-key-file}. +@item +@code{HEX(string)} now returns the characters in string converted to +hexadecimal. +@item Fixed problem with @code{GRANT} when using @code{lower_case_table_names == 1}. @item Changed @code{SELECT ... IN SHARE MODE} to diff --git a/include/mysql_com.h b/include/mysql_com.h index ca6c136d761..a4e0b5f0223 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -80,6 +80,7 @@ enum enum_server_command {COM_SLEEP,COM_QUIT,COM_INIT_DB,COM_QUERY, /* RESET (remove all queries) from query cache */ #define REFRESH_QUERY_CACHE 65536 #define REFRESH_QUERY_CACHE_FREE 0x20000L /* pack query cache */ +#define REFRESH_DES_KEY_FILE 0x40000L #define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ #define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */ diff --git a/myisam/ft_update.c b/myisam/ft_update.c index 01dd67b97fa..a946b0beabe 100644 --- a/myisam/ft_update.c +++ b/myisam/ft_update.c @@ -75,7 +75,8 @@ uint _mi_ft_segiterator(register FT_SEG_ITERATOR *ftsi) if (ftsi->seg->flag & HA_BLOB_PART) { ftsi->len=_mi_calc_blob_length(ftsi->seg->bit_start,ftsi->pos); - memcpy_fixed(&ftsi->pos,ftsi->pos+ftsi->seg->bit_start,sizeof(char*)); + memcpy_fixed((char*) &ftsi->pos, ftsi->pos+ftsi->seg->bit_start, + sizeof(char*)); set_if_smaller(ftsi->len,ftsi->seg->length); return 1; } @@ -107,11 +108,11 @@ FT_WORD * _mi_ft_parserecord(MI_INFO *info, uint keynr, { TREE ptree; - bzero(&ptree, sizeof(ptree)); - if (_mi_ft_parse(& ptree, info, keynr, record)) + bzero((char*) &ptree, sizeof(ptree)); + if (_mi_ft_parse(&ptree, info, keynr, record)) return NULL; - return ft_linearize(/*info, keynr, keybuf, */ & ptree); + return ft_linearize(/*info, keynr, keybuf, */ &ptree); } static int _mi_ft_store(MI_INFO *info, uint keynr, byte *keybuf, diff --git a/mysql-test/mysql-test-run.sh b/mysql-test/mysql-test-run.sh index 4a9764efa5b..f2f0c2a4234 100644 --- a/mysql-test/mysql-test-run.sh +++ b/mysql-test/mysql-test-run.sh @@ -76,7 +76,6 @@ sleep_until_file_exists () exit 1; } - # No paths below as we can't be sure where the program is! BASENAME=`which basename | head -1` @@ -993,7 +992,7 @@ run_testcase () then if [ -f $master_opt_file ] ; then - EXTRA_MASTER_OPT=`$CAT $master_opt_file` + EXTRA_MASTER_OPT=`$CAT $master_opt_file | $SED -e "s;\\$MYSQL_TEST_DIR;$MYSQL_TEST_DIR;"` stop_master start_master else @@ -1008,7 +1007,7 @@ run_testcase () if [ -f $slave_opt_file ] ; then - EXTRA_SLAVE_OPT=`$CAT $slave_opt_file` + EXTRA_SLAVE_OPT=`$CAT $slave_opt_file | $SED -e "s;\\$MYSQL_TEST_DIR;$MYSQL_TEST_DIR;"` do_slave_restart=1 else if [ ! -z "$EXTRA_SLAVE_OPT" ] || [ x$SLAVE_RUNNING != x1 ] ; diff --git a/mysql-test/r/func_crypt.result b/mysql-test/r/func_crypt.result index 54f2865454d..cf464ec21b2 100644 --- a/mysql-test/r/func_crypt.result +++ b/mysql-test/r/func_crypt.result @@ -1,3 +1,6 @@ select length(encrypt('foo', 'ff')) <> 0; length(encrypt('foo', 'ff')) <> 0 1 +select password('test'),length(encrypt('test')),encrypt('test','aa'); +password('test') length(encrypt('test')) encrypt('test','aa') +378b243e220ca493 13 aaqPiZY5xR5l. diff --git a/mysql-test/r/func_encrypt.result b/mysql-test/r/func_encrypt.result Binary files differindex 9d16e2c7dfe..39c734999b2 100644 --- a/mysql-test/r/func_encrypt.result +++ b/mysql-test/r/func_encrypt.result diff --git a/mysql-test/r/func_str.result b/mysql-test/r/func_str.result index 28a4870cfc6..5aca1f6b021 100644 --- a/mysql-test/r/func_str.result +++ b/mysql-test/r/func_str.result @@ -77,9 +77,6 @@ this is a REAL test select soundex(''),soundex('he'),soundex('hello all folks'); soundex('') soundex('he') soundex('hello all folks') H000 H4142 -select password('test'),length(encrypt('test')),encrypt('test','aa'); -password('test') length(encrypt('test')) encrypt('test','aa') -378b243e220ca493 13 aaqPiZY5xR5l. select md5('hello'); md5('hello') 5d41402abc4b2a76b9719d911017c592 diff --git a/mysql-test/std_data/des_key_file b/mysql-test/std_data/des_key_file new file mode 100644 index 00000000000..fa53802d449 --- /dev/null +++ b/mysql-test/std_data/des_key_file @@ -0,0 +1,4 @@ +5 default_password +1 password1 +4 password4 +2 password2 diff --git a/mysql-test/t/func_crypt.test b/mysql-test/t/func_crypt.test index ddc0816e301..f01504d3691 100644 --- a/mysql-test/t/func_crypt.test +++ b/mysql-test/t/func_crypt.test @@ -1,2 +1,3 @@ - select length(encrypt('foo', 'ff')) <> 0; +--replace_result $1$aa$4OSUA5cjdx0RUQ08opV27/ aaqPiZY5xR5l. +select password('test'),length(encrypt('test')),encrypt('test','aa'); diff --git a/mysql-test/t/func_encrypt-master.opt b/mysql-test/t/func_encrypt-master.opt new file mode 100644 index 00000000000..0b042f52e9a --- /dev/null +++ b/mysql-test/t/func_encrypt-master.opt @@ -0,0 +1 @@ +--des-key-file=$MYSQL_TEST_DIR/std_data/des_key_file diff --git a/mysql-test/t/func_encrypt.test b/mysql-test/t/func_encrypt.test index 9c59281328c..3b6acc54ec9 100644 --- a/mysql-test/t/func_encrypt.test +++ b/mysql-test/t/func_encrypt.test @@ -1,46 +1,67 @@ -- source include/have_openssl.inc -use test; -drop table if exists x; -create table x (x blob); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('a','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','a')); -insert into x values (des_encrypt('ab','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','ab')); -insert into x values (des_encrypt('abc','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abc')); -insert into x values (des_encrypt('abcd','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcd')); -insert into x values (des_encrypt('abcde','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcde')); -insert into x values (des_encrypt('abcdef','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdef')); -insert into x values (des_encrypt('abcdefg','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefg')); -insert into x values (des_encrypt('abcdefgh','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefgh')); -insert into x values (des_encrypt('abcdefghi','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghi')); -insert into x values (des_encrypt('abcdefghij','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghij')); -insert into x values (des_encrypt('abcdefghijk','The quick red fox jumped over the lazy brown dog')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghijk')); -insert into x values (des_encrypt('The quick red fox jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('quick red fox jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('red fox jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('fox jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('the lazy brown dog','sabakala')); -insert into x values (des_encrypt('lazy brown dog','sabakala')); -insert into x values (des_encrypt('brown dog','sabakala')); -insert into x values (des_encrypt('dog','sabakala')); -insert into x values (des_encrypt('jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('jumped over the lazy brown dog','sabakala')); -insert into x values (des_encrypt('jumped over the lazy brown dog','sabakala')); -select * from x; -select des_decrypt(x,'sabakala') from x; -drop table x; +drop table if exists t1; +create table t1 (x blob); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('a','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','a')); +insert into t1 values (des_encrypt('ab','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','ab')); +insert into t1 values (des_encrypt('abc','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abc')); +insert into t1 values (des_encrypt('abcd','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcd')); +insert into t1 values (des_encrypt('abcde','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcde')); +insert into t1 values (des_encrypt('abcdef','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdef')); +insert into t1 values (des_encrypt('abcdefg','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefg')); +insert into t1 values (des_encrypt('abcdefgh','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefgh')); +insert into t1 values (des_encrypt('abcdefghi','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghi')); +insert into t1 values (des_encrypt('abcdefghij','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghij')); +insert into t1 values (des_encrypt('abcdefghijk','The quick red fox jumped over the lazy brown dog')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','abcdefghijk')); +insert into t1 values (des_encrypt('The quick red fox jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('quick red fox jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('red fox jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('fox jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('brown dog','sabakala')); +insert into t1 values (des_encrypt('dog','sabakala')); +insert into t1 values (des_encrypt('dog!','sabakala')); +insert into t1 values (des_encrypt('dog!!','sabakala')); +insert into t1 values (des_encrypt('dog!!!','sabakala')); +insert into t1 values (des_encrypt('dog!!!!','sabakala')); +insert into t1 values (des_encrypt('dog!!!!!','sabakala')); +insert into t1 values (des_encrypt('jumped over the lazy brown dog','sabakala')); +insert into t1 values (des_encrypt('jumped over the lazy brown dog','sabakala')); +select hex(x), hex(des_decrypt(x,'sabakala')) from t1; +select des_decrypt(x,'sabakala') as s from t1 having s like '%dog%'; +drop table t1; +# +# Test default keys +# +select hex(des_encrypt("hello")),des_decrypt(des_encrypt("hello")); +select des_decrypt(des_encrypt("hello",4)); +select des_decrypt(des_encrypt("hello",'test'),'test'); +select hex(des_encrypt("hello")),hex(des_encrypt("hello",5)),hex(des_encrypt("hello",'default_password')); +select des_decrypt(des_encrypt("hello"),'default_password'); +select des_decrypt(des_encrypt("hello",4),'password4'); +# Test flush +SET @a=des_decrypt(des_encrypt("hello")); +flush des_key_file; +select @a = des_decrypt(des_encrypt("hello")); + +# Test usage of wrong password +select hex("hello"); +select hex(des_decrypt(des_encrypt("hello",4),'password2')); +select hex(des_decrypt(des_encrypt("hello","hidden"))); diff --git a/mysql-test/t/func_str.test b/mysql-test/t/func_str.test index 27d7f2e68d5..fd6ff0d70e7 100644 --- a/mysql-test/t/func_str.test +++ b/mysql-test/t/func_str.test @@ -35,8 +35,6 @@ select insert('txs',2,1,'hi'),insert('is ',4,0,'a'),insert('txxxxt',2,4,'es'); select replace('aaaa','a','b'),replace('aaaa','aa','b'),replace('aaaa','a','bb'),replace('aaaa','','b'),replace('bbbb','a','c'); select replace(concat(lcase(concat('THIS',' ','IS',' ','A',' ')),ucase('false'),' ','test'),'FALSE','REAL') ; select soundex(''),soundex('he'),soundex('hello all folks'); ---replace_result $1$aa$4OSUA5cjdx0RUQ08opV27/ aaqPiZY5xR5l. -select password('test'),length(encrypt('test')),encrypt('test','aa'); select md5('hello'); select repeat('monty',5),concat('*',space(5),'*'); select reverse('abc'),reverse('abcd'); diff --git a/sql/des_key_file.cc b/sql/des_key_file.cc index c6ac9ab6059..d9c924b5a3c 100644 --- a/sql/des_key_file.cc +++ b/sql/des_key_file.cc @@ -19,25 +19,37 @@ #ifdef HAVE_OPENSSL +struct st_des_keyschedule des_keyschedule[10]; +uint des_default_key; +pthread_mutex_t LOCK_des_key_file; +static int initialized; + /* Function which loads DES keys from plaintext file into memory on MySQL - server startup and on command FLUSH DES_KEYS. Blame tonu@spam.ee on bugs ;) -*/ + server startup and on command FLUSH DES_KEY_FILE. + Blame tonu@spam.ee on bugs ;) -struct st_des_keyschedule des_keyschedule[10]; -uint des_default_key; + RETURN + 0 ok + 1 Error +*/ -void +bool load_des_key_file(const char *file_name) { + bool result=1; File file; - des_cblock ivec={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; - char offset; IO_CACHE io; DBUG_ENTER("load_des_key_file"); DBUG_PRINT("enter",("name: %s",file_name)); - VOID(pthread_mutex_lock(&LOCK_open)); + if (!initialized) + { + initialized=1; + pthread_mutex_init(&LOCK_des_key_file,MY_MUTEX_INIT_FAST); + } + + VOID(pthread_mutex_lock(&LOCK_des_key_file)); if ((file=my_open(file_name,O_RDONLY | O_BINARY ,MYF(MY_WME))) < 0 || init_io_cache(&io, file, IO_SIZE*2, READ_CACHE, 0, 0, MYF(MY_WME))) goto error; @@ -47,7 +59,7 @@ load_des_key_file(const char *file_name) for (;;) { char *start, *end; - char buf[1024]; + char buf[1024], offset; st_des_keyblock keyblock; uint length; @@ -60,10 +72,12 @@ load_des_key_file(const char *file_name) // Remove newline and possible other control characters for (start=buf+1 ; isspace(*start) ; start++) ; end=buf+length; - for (end=strend(buf) ; end > start && iscntrl(end[-1]) ; end--) ; + for (end=strend(buf) ; end > start && !isgraph(end[-1]) ; end--) ; if (start != end) { + des_cblock ivec; + bzero((char*) &ivec,sizeof(ivec)); // We make good 24-byte (168 bit) key from given plaintext key with MD5 EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, (uchar *) start, (int) (end-start),1, @@ -76,11 +90,10 @@ load_des_key_file(const char *file_name) des_default_key= (uint) offset; // use first as def. } } - else - { - DBUG_PRINT("des",("wrong offset: %c",offset)); - } + else if (offset != '#') + sql_print_error("load_des_file: Found wrong key_number: %c",offset); } + result=0; error: if (file >= 0) @@ -88,7 +101,7 @@ error: my_close(file,MYF(0)); end_io_cache(&io); } - VOID(pthread_mutex_unlock(&LOCK_open)); - DBUG_VOID_RETURN; + VOID(pthread_mutex_unlock(&LOCK_des_key_file)); + DBUG_RETURN(result); } #endif /* HAVE_OPENSSL */ diff --git a/sql/item_create.cc b/sql/item_create.cc index 71dc99f2558..a9567414b0b 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -151,8 +151,7 @@ Item *create_func_get_lock(Item* a, Item *b) Item *create_func_hex(Item *a) { - return new Item_func_conv(a,new Item_int((int32) 10,2), - new Item_int((int32) 16,2)); + return new Item_func_hex(a); } Item *create_func_inet_ntoa(Item* a) diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 792c76ebc0d..c64fdc7a049 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -209,78 +209,84 @@ void Item_func_concat::fix_length_and_dec() Function des_encrypt() by tonu@spam.ee & monty Works only if compiled with OpenSSL library support. This returns a binary string where first character is - CHAR(128 | tail-length << 4 | key-number). - If one uses a string key key_number is 0. + CHAR(128 | key-number). + If one uses a string key key_number is 127. Encryption result is longer than original by formula: - new_length= (8-(original_length % 8))+1 + new_length= org_length + (8-(org_length % 8))+1 */ String *Item_func_des_encrypt::val_str(String *str) { #ifdef HAVE_OPENSSL - des_cblock ivec={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + des_cblock ivec; struct st_des_keyblock keyblock; struct st_des_keyschedule keyschedule; - struct st_des_keyschedule *keyschedule_ptr; const char *append_str="********"; uint key_number, res_length, tail; String *res= args[0]->val_str(str); if ((null_value=args[0]->null_value)) return 0; - if (res->length() == 0) + if ((res_length=res->length()) == 0) return &empty_string; if (arg_count == 1) - keyschedule_ptr= &des_keyschedule[key_number=des_default_key]; + { + /* Protect against someone doing FLUSH DES_KEY_FILE */ + VOID(pthread_mutex_lock(&LOCK_des_key_file)); + keyschedule= des_keyschedule[key_number=des_default_key]; + VOID(pthread_mutex_unlock(&LOCK_des_key_file)); + } else if (args[1]->result_type() == INT_RESULT) { key_number= (uint) args[1]->val_int(); if (key_number > 9) goto error; - keyschedule_ptr= &des_keyschedule[key_number]; + VOID(pthread_mutex_lock(&LOCK_des_key_file)); + keyschedule= des_keyschedule[key_number]; + VOID(pthread_mutex_unlock(&LOCK_des_key_file)); } else { - uint tail,res_length; String *keystr=args[1]->val_str(&tmp_value); if (!keystr) goto error; - key_number=15; // User key string + key_number=127; // User key string /* We make good 24-byte (168 bit) key from given plaintext key with MD5 */ - keyschedule_ptr= &keyschedule; + bzero((char*) &ivec,sizeof(ivec)); EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, (uchar*) keystr->ptr(), (int) keystr->length(), 1, (uchar*) &keyblock,ivec); - des_set_key_unchecked(&keyblock.key1,keyschedule_ptr->ks1); - des_set_key_unchecked(&keyblock.key2,keyschedule_ptr->ks2); - des_set_key_unchecked(&keyblock.key3,keyschedule_ptr->ks3); + des_set_key_unchecked(&keyblock.key1,keyschedule.ks1); + des_set_key_unchecked(&keyblock.key2,keyschedule.ks2); + des_set_key_unchecked(&keyblock.key3,keyschedule.ks3); } /* The problem: DES algorithm requires original data to be in 8-bytes - chunks. Missing bytes get filled with zeros and result of encryption - can be up to 7 bytes longer than original string. When decrypted, + chunks. Missing bytes get filled with '*'s and result of encryption + can be up to 8 bytes longer than original string. When decrypted, we do not know the size of original string :( - We add one byte with value 0x1..0x8 as the second byte to original - plaintext marking change of string length. + We add one byte with value 0x1..0x8 as the last byte of the padded + string marking change of string length. */ - tail= (7-(res->length()+7) % 8); // 0..7 marking extra length - res_length=res->length()+tail+1; - if (tail && res->append(append_str, tail) || tmp_value.alloc(res_length)) + tail= (8-(res_length) % 8); // 1..8 marking extra length + res_length+=tail; + if (tail && res->append(append_str, tail) || tmp_value.alloc(res_length+1)) goto error; - - tmp_value.length(res_length); - tmp_value[0]=(char) (128 | tail << 4 | key_number); + (*res)[res_length-1]=tail; // save extra length + tmp_value.length(res_length+1); + tmp_value[0]=(char) (128 | key_number); // Real encryption + bzero((char*) &ivec,sizeof(ivec)); des_ede3_cbc_encrypt((const uchar*) (res->ptr()), (uchar*) (tmp_value.ptr()+1), - res->length(), - keyschedule_ptr->ks1, - keyschedule_ptr->ks2, - keyschedule_ptr->ks3, + res_length, + keyschedule.ks1, + keyschedule.ks2, + keyschedule.ks3, &ivec, TRUE); return &tmp_value; @@ -295,24 +301,27 @@ String *Item_func_des_decrypt::val_str(String *str) { #ifdef HAVE_OPENSSL des_key_schedule ks1, ks2, ks3; - des_cblock ivec={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + des_cblock ivec; struct st_des_keyblock keyblock; struct st_des_keyschedule keyschedule; - struct st_des_keyschedule *keyschedule_ptr; String *res= args[0]->val_str(str); + uint length=res->length(),tail; if ((null_value=args[0]->null_value)) return 0; - if (res->length() < 9 || (res->length() % 8) != 1 || !((*res)[0] & 128)) + length=res->length(); + if (length < 9 || (length % 8) != 1 || !((*res)[0] & 128)) return res; // Skip decryption if not encrypted if (arg_count == 1) // If automatic uncompression { - uint key_number=(uint) (*res)[0] & 15; + uint key_number=(uint) (*res)[0] & 127; // Check if automatic key and that we have privilege to uncompress using it if (!(current_thd->master_access & PROCESS_ACL) || key_number > 9) goto error; - keyschedule_ptr= &des_keyschedule[key_number]; + VOID(pthread_mutex_lock(&LOCK_des_key_file)); + keyschedule= des_keyschedule[key_number]; + VOID(pthread_mutex_unlock(&LOCK_des_key_file)); } else { @@ -321,26 +330,30 @@ String *Item_func_des_decrypt::val_str(String *str) if (!keystr) goto error; - keyschedule_ptr= &keyschedule; + bzero((char*) &ivec,sizeof(ivec)); EVP_BytesToKey(EVP_des_ede3_cbc(),EVP_md5(),NULL, (uchar*) keystr->ptr(),(int) keystr->length(), 1,(uchar*) &keyblock,ivec); // Here we set all 64-bit keys (56 effective) one by one - des_set_key_unchecked(&keyblock.key1,keyschedule_ptr->ks1); - des_set_key_unchecked(&keyblock.key2,keyschedule_ptr->ks2); - des_set_key_unchecked(&keyblock.key3,keyschedule_ptr->ks3); + des_set_key_unchecked(&keyblock.key1,keyschedule.ks1); + des_set_key_unchecked(&keyblock.key2,keyschedule.ks2); + des_set_key_unchecked(&keyblock.key3,keyschedule.ks3); } - if (tmp_value.alloc(res->length()-1)) + if (tmp_value.alloc(length-1)) goto error; - /* Restore old length of key */ - tmp_value.length(res->length()-1-(((uchar) (*res)[0] >> 4) & 7)); + + bzero((char*) &ivec,sizeof(ivec)); des_ede3_cbc_encrypt((const uchar*) res->ptr()+1, (uchar*) (tmp_value.ptr()), - res->length()-1, - keyschedule_ptr->ks1, - keyschedule_ptr->ks2, - keyschedule_ptr->ks3, + length-1, + keyschedule.ks1, + keyschedule.ks2, + keyschedule.ks3, &ivec, FALSE); + /* Restore old length of key */ + if ((tail=(uint) (uchar) tmp_value[length-2]) > 8) + goto error; // Wrong key + tmp_value.length(length-1-tail); return &tmp_value; error: @@ -1274,9 +1287,9 @@ String *Item_func_soundex::val_str(String *str) if ((null_value=args[0]->null_value)) return 0; /* purecov: inspected */ - if (str_value.alloc(max(res->length(),4))) + if (tmp_value.alloc(max(res->length(),4))) return str; /* purecov: inspected */ - char *to= (char *) str_value.ptr(); + char *to= (char *) tmp_value.ptr(); char *from= (char *) res->ptr(), *end=from+res->length(); while (from != end && isspace(*from)) // Skip pre-space @@ -1300,11 +1313,11 @@ String *Item_func_soundex::val_str(String *str) last_ch = ch; // save code of last input letter } // for next double-letter check } - for (end=(char*) str_value.ptr()+4 ; to < end ; to++) + for (end=(char*) tmp_value.ptr()+4 ; to < end ; to++) *to = '0'; *to=0; // end string - str_value.length((uint) (to-str_value.ptr())); - return &str_value; + tmp_value.length((uint) (to-tmp_value.ptr())); + return &tmp_value; } @@ -1759,6 +1772,45 @@ String *Item_func_conv::val_str(String *str) return str; } + +String *Item_func_hex::val_str(String *str) +{ + if (args[0]->result_type() != STRING_RESULT) + { + /* Return hex of unsigned longlong value */ + longlong dec= args[0]->val_int(); + char ans[65],*ptr; + if ((null_value= args[0]->null_value)) + return 0; + ptr= longlong2str(dec,ans,16); + if (str->copy(ans,(uint32) (ptr-ans))) + return &empty_string; // End of memory + return str; + } + + /* Convert given string to a hex string, character by character */ + String *res= args[0]->val_str(str); + const char *from, *end; + char *to; + if (!res || tmp_value.alloc(res->length()*2)) + { + null_value=1; + return 0; + } + null_value=0; + tmp_value.length(res->length()*2); + for (from=res->ptr(), end=from+res->length(), to= (char*) tmp_value.ptr(); + from != end ; + from++, to+=2) + { + uint tmp=(uint) (uchar) *from; + to[0]=_dig_vec[tmp >> 4]; + to[1]=_dig_vec[tmp & 15]; + } + return &tmp_value; +} + + #include <my_dir.h> // For my_stat String *Item_load_file::val_str(String *str) diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index ba2d6ffc3f0..e92dcf806db 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -297,6 +297,7 @@ public: class Item_func_soundex :public Item_str_func { + String tmp_value; public: Item_func_soundex(Item *a) :Item_str_func(a) {} String *val_str(String *); @@ -412,6 +413,18 @@ public: void fix_length_and_dec() { decimals=0; max_length=64; } }; + +class Item_func_hex :public Item_str_func +{ + String tmp_value; +public: + Item_func_hex(Item *a) :Item_str_func(a) {} + const char *func_name() const { return "hex"; } + String *val_str(String *); + void fix_length_and_dec() { decimals=0; max_length=args[0]->max_length*2; } +}; + + class Item_func_binary :public Item_str_func { public: diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index e65f7679cae..2b1bb9cae0e 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -672,7 +672,7 @@ String *Item_func_date_format::val_str(String *str) else size=format_length(format); if (format == str) - str=&str_value; // Save result here + str= &str_value; // Save result here if (str->alloc(size)) { null_value=1; diff --git a/sql/lex.h b/sql/lex.h index d20141a560e..d72d2ff63a4 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -111,6 +111,7 @@ static SYMBOL symbols[] = { { "DAY_SECOND", SYM(DAY_SECOND_SYM),0,0}, { "DEC", SYM(DECIMAL_SYM),0,0}, { "DECIMAL", SYM(DECIMAL_SYM),0,0}, + { "DES_KEY_FILE", SYM(DES_KEY_FILE),0,0}, { "DEFAULT", SYM(DEFAULT),0,0}, { "DELAYED", SYM(DELAYED_SYM),0,0}, { "DELAY_KEY_WRITE", SYM(DELAY_KEY_WRITE_SYM),0,0}, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index a6859e31bde..fece60dd49c 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -263,7 +263,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list); bool mysql_change_db(THD *thd,const char *name); void mysql_parse(THD *thd,char *inBuf,uint length); void mysql_init_select(LEX *lex); -void mysql_new_select(LEX *lex); +bool mysql_new_select(LEX *lex); void init_max_user_conn(void); void free_max_user_conn(void); pthread_handler_decl(handle_one_connection,arg); @@ -405,9 +405,11 @@ struct st_des_keyschedule { des_key_schedule ks1, ks2, ks3; }; +extern char *des_key_file; extern struct st_des_keyschedule des_keyschedule[10]; extern uint des_default_key; -void load_des_key_file(const char *file_name); +extern pthread_mutex_t LOCK_des_key_file; +bool load_des_key_file(const char *file_name); #endif /* HAVE_OPENSSL */ /* sql_list.c */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a8b9a799c56..786c0c2ad38 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -242,7 +242,7 @@ static char glob_hostname[FN_REFLEN]; #include "sslopt-vars.h" #ifdef HAVE_OPENSSL -static char * des_key_file = 0; +char *des_key_file = 0; struct st_VioSSLAcceptorFd * ssl_acceptor_fd = 0; #endif /* HAVE_OPENSSL */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 12646f0737f..ebe8fc979bf 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2431,9 +2431,10 @@ mysql_init_select(LEX *lex) { SELECT_LEX *select_lex = lex->select; select_lex->where=select_lex->having=0; - select_lex->select_limit=current_thd->default_select_limit; + select_lex->select_limit=lex->thd->default_select_limit; select_lex->offset_limit=0; - select_lex->options=0; select_lex->linkage=UNSPECIFIED_TYPE; + select_lex->options=0; + select_lex->linkage=UNSPECIFIED_TYPE; lex->exchange = 0; lex->proc_list.first=0; select_lex->order_list.elements=select_lex->group_list.elements=0; @@ -2444,18 +2445,25 @@ mysql_init_select(LEX *lex) select_lex->next = (SELECT_LEX *)NULL; } -void +bool mysql_new_select(LEX *lex) { SELECT_LEX *select_lex = (SELECT_LEX *) lex->thd->calloc(sizeof(SELECT_LEX)); + if (!select_lex) + return 1; lex->select->next=select_lex; lex->select=select_lex; select_lex->table_list.next= (byte**) &select_lex->table_list.first; - select_lex->item_list.empty(); select_lex->when_list.empty(); - select_lex->expr_list.empty(); select_lex->interval_list.empty(); - select_lex->use_index.empty(); select_lex->ftfunc_list.empty(); + select_lex->item_list.empty(); + select_lex->when_list.empty(); + select_lex->expr_list.empty(); + select_lex->interval_list.empty(); + select_lex->use_index.empty(); + select_lex->ftfunc_list.empty(); + return 0; } + void mysql_parse(THD *thd,char *inBuf,uint length) { @@ -3035,7 +3043,13 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables) reset_master(); if (options & REFRESH_SLAVE) reset_slave(); - +#ifdef OPENSSL + if (options & REFRESH_DES_KEY_FILE) + { + if (des_key_file) + result=load_des_key_file(des_key_file); + } +#endif return result; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ea105f3be50..e76998e9d7d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -171,13 +171,14 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token DEMAND_SYM %token DESC %token DESCRIBE -%token DIRECTORY_SYM -%token DISTINCT +%token DES_KEY_FILE %token DISABLE_SYM +%token DISTINCT %token DYNAMIC_SYM %token ENABLE_SYM %token ENCLOSED %token ESCAPED +%token DIRECTORY_SYM %token ESCAPE_SYM %token EXISTS %token EXTENDED_SYM @@ -2106,13 +2107,7 @@ order_dir: limit_clause: - /* empty */ - { - SELECT_LEX *sel=Select; - sel->select_limit= (Lex->sql_command == SQLCOM_HA_READ) ? - 1 : current_thd->default_select_limit; - sel->offset_limit= 0L; - } + /* empty */ {} | LIMIT ULONG_NUM { SELECT_LEX *sel=Select; @@ -2511,13 +2506,14 @@ show_param: TEXT_STRING AND MASTER_LOG_POS_SYM EQ ulonglong_num AND MASTER_LOG_SEQ_SYM EQ ULONG_NUM AND MASTER_SERVER_ID_SYM EQ ULONG_NUM - { - Lex->sql_command = SQLCOM_SHOW_NEW_MASTER; - Lex->mi.log_file_name = $8.str; - Lex->mi.pos = $12; - Lex->mi.last_log_seq = $16; - Lex->mi.server_id = $20; - } + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_SHOW_NEW_MASTER; + lex->mi.log_file_name = $8.str; + lex->mi.pos = $12; + lex->mi.last_log_seq = $16; + lex->mi.server_id = $20; + } | MASTER_SYM LOGS_SYM { Lex->sql_command = SQLCOM_SHOW_BINLOGS; @@ -2526,10 +2522,13 @@ show_param: { Lex->sql_command = SQLCOM_SHOW_SLAVE_HOSTS; } - | BINLOG_SYM EVENTS_SYM binlog_in binlog_from limit_clause + | BINLOG_SYM EVENTS_SYM binlog_in binlog_from { - Lex->sql_command = SQLCOM_SHOW_BINLOG_EVENTS; - } + LEX *lex=Lex; + lex->sql_command = SQLCOM_SHOW_BINLOG_EVENTS; + lex->select->select_limit= lex->thd->default_select_limit; + lex->select->offset_limit= 0L; + } limit_clause | keys_or_index FROM table_ident opt_db { Lex->sql_command= SQLCOM_SHOW_KEYS; @@ -2642,6 +2641,7 @@ flush_option: | STATUS_SYM { Lex->type|= REFRESH_STATUS; } | SLAVE { Lex->type|= REFRESH_SLAVE; } | MASTER_SYM { Lex->type|= REFRESH_MASTER; } + | DES_KEY_FILE { Lex->type|= REFRESH_DES_KEY_FILE; } opt_table_list: /* empty */ {} @@ -2912,27 +2912,28 @@ keyword: | CIPHER_SYM {} | CLOSE_SYM {} | COMMENT_SYM {} - | COMMIT_SYM {} | COMMITTED_SYM {} + | COMMIT_SYM {} | COMPRESSED_SYM {} | CONCURRENT {} | DATA_SYM {} | DATETIME {} | DATE_SYM {} | DAY_SYM {} - | DIRECTORY_SYM {} | DELAY_KEY_WRITE_SYM {} | DEMAND_SYM {} - | DISABLE_SYM {} + | DES_KEY_FILE {} + | DIRECTORY_SYM {} | DUMPFILE {} | DYNAMIC_SYM {} - | ENABLE_SYM {} | END {} | ENUM {} | ESCAPE_SYM {} | EVENTS_SYM {} | EXTENDED_SYM {} | FAST_SYM {} + | DISABLE_SYM {} + | ENABLE_SYM {} | FULL {} | FILE_SYM {} | FIRST_SYM {} @@ -3296,6 +3297,8 @@ handler: LEX *lex=Lex; lex->sql_command = SQLCOM_HA_READ; lex->ha_rkey_mode= HA_READ_KEY_EXACT; /* Avoid purify warnings */ + lex->select->select_limit= 1; + lex->select->offset_limit= 0L; if (!add_table_to_list($2,0,0)) YYABORT; } @@ -3581,9 +3584,8 @@ union_list: net_printf(&lex->thd->net, ER_WRONG_USAGE,"UNION","INTO"); YYABORT; } - if (lex->select->linkage == NOT_A_SELECT) + if (lex->select->linkage == NOT_A_SELECT || mysql_new_select(lex)) YYABORT; - mysql_new_select(lex); lex->select->linkage=UNION_TYPE; } select_init @@ -3597,11 +3599,11 @@ optional_order_or_limit: | { LEX *lex=Lex; - if (!lex->select->braces) - YYABORT; - mysql_new_select(lex); + if (!lex->select->braces || mysql_new_select(lex)) + YYABORT; mysql_init_select(lex); lex->select->linkage=NOT_A_SELECT; + lex->select->select_limit=lex->thd->default_select_limit; } opt_order_clause limit_clause |