mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			437 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			437 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|    Copyright (c) 2005, 2010, Oracle and/or its affiliates.
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
 | |
| 
 | |
| #include "mariadb.h"
 | |
| #include "sql_priv.h"
 | |
| #include <time.h>
 | |
| 
 | |
| #ifndef MYSQL_CLIENT
 | |
| #include "sql_class.h"                          // THD
 | |
| #include "field.h"
 | |
| #endif
 | |
| 
 | |
| #define DIG_BASE     1000000000
 | |
| #define DIG_PER_DEC1 9
 | |
| #define ROUND_UP(X)  (((X)+DIG_PER_DEC1-1)/DIG_PER_DEC1)
 | |
| 
 | |
| #ifndef MYSQL_CLIENT
 | |
| /**
 | |
|   report result of decimal operation.
 | |
| 
 | |
|   @param result  decimal library return code (E_DEC_* see include/decimal.h)
 | |
| 
 | |
|   @todo
 | |
|     Fix error messages
 | |
| 
 | |
|   @return
 | |
|     result
 | |
| */
 | |
| 
 | |
| int decimal_operation_results(int result, const char *value, const char *type)
 | |
| {
 | |
|   /* Avoid calling current_thd on default path */
 | |
|   if (likely(result == E_DEC_OK))
 | |
|     return(result);
 | |
|   
 | |
|   THD *thd= current_thd;
 | |
|   switch (result) {
 | |
|   case E_DEC_TRUNCATED:
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
| 			ER_DATA_TRUNCATED, ER_THD(thd, ER_DATA_TRUNCATED),
 | |
| 			value, type);
 | |
|     break;
 | |
|   case E_DEC_OVERFLOW:
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                         ER_DATA_OVERFLOW, ER_THD(thd, ER_DATA_OVERFLOW),
 | |
| 			value, type);
 | |
|     break;
 | |
|   case E_DEC_DIV_ZERO:
 | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
| 			ER_DIVISION_BY_ZERO, ER_THD(thd, ER_DIVISION_BY_ZERO));
 | |
|     break;
 | |
|   case E_DEC_BAD_NUM:
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
| 			ER_BAD_DATA, ER_THD(thd, ER_BAD_DATA),
 | |
| 			value, type);
 | |
|     break;
 | |
|   case E_DEC_OOM:
 | |
|     my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | |
|     break;
 | |
|   default:
 | |
|     DBUG_ASSERT(0);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Converting decimal to string
 | |
| 
 | |
|   @details Convert given my_decimal to String; allocate buffer as needed.
 | |
| 
 | |
|   @param[in]   mask        what problems to warn on (mask of E_DEC_* values)
 | |
|   @param[in]   d           the decimal to print
 | |
|   @param[in]   fixed_prec  overall number of digits if ZEROFILL, 0 otherwise
 | |
|   @param[in]   fixed_dec   number of decimal places (if fixed_prec != 0)
 | |
|   @param[in]   filler      what char to pad with (ZEROFILL et al.)
 | |
|   @param[out]  *str        where to store the resulting string
 | |
| 
 | |
|   @return error coce
 | |
|     @retval E_DEC_OK
 | |
|     @retval E_DEC_TRUNCATED
 | |
|     @retval E_DEC_OVERFLOW
 | |
|     @retval E_DEC_OOM
 | |
| */
 | |
| 
 | |
| int my_decimal::to_string_native(String *str, uint fixed_prec, uint fixed_dec,
 | |
|                                  char filler, uint mask) const
 | |
| {
 | |
|   /*
 | |
|     Calculate the size of the string: For DECIMAL(a,b), fixed_prec==a
 | |
|     holds true iff the type is also ZEROFILL, which in turn implies
 | |
|     UNSIGNED. Hence the buffer for a ZEROFILLed value is the length
 | |
|     the user requested, plus one for a possible decimal point, plus
 | |
|     one if the user only wanted decimal places, but we force a leading
 | |
|     zero on them, plus one for the '\0' terminator. Because the type
 | |
|     is implicitly UNSIGNED, we do not need to reserve a character for
 | |
|     the sign. For all other cases, fixed_prec will be 0, and
 | |
|     my_decimal_string_length() will be called instead to calculate the
 | |
|     required size of the buffer.
 | |
|   */
 | |
|   int length= (fixed_prec
 | |
|                ? (fixed_prec + ((fixed_prec == fixed_dec) ? 1 : 0) + 1)
 | |
|                : my_decimal_string_length(this));
 | |
|   int result;
 | |
|   if (str->alloc(length+1))                     // Alloc also space for \0
 | |
|     return check_result(mask, E_DEC_OOM);
 | |
|   result= decimal2string(this, (char*) str->ptr(),
 | |
|                          &length, (int)fixed_prec, fixed_dec,
 | |
|                          filler);
 | |
|   str->length(length);
 | |
|   str->set_charset(&my_charset_numeric);
 | |
|   return check_result(mask, result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Converting decimal to string with character set conversion
 | |
| 
 | |
|   @details Convert given my_decimal to String; allocate buffer as needed.
 | |
| 
 | |
|   @param[in]   mask        what problems to warn on (mask of E_DEC_* values)
 | |
|   @param[in]   val         the decimal to print
 | |
|   @param[in]   fixed_prec  overall number of digits if ZEROFILL, 0 otherwise
 | |
|   @param[in]   fixed_dec   number of decimal places (if fixed_prec != 0)
 | |
|   @param[in]   filler      what char to pad with (ZEROFILL et al.)
 | |
|   @param[out]  *str        where to store the resulting string
 | |
|   @param[in]   cs          character set
 | |
| 
 | |
|   @return error coce
 | |
|     @retval E_DEC_OK
 | |
|     @retval E_DEC_TRUNCATED
 | |
|     @retval E_DEC_OVERFLOW
 | |
|     @retval E_DEC_OOM
 | |
| 
 | |
|   Would be great to make it a method of the String class,
 | |
|   but this would need to include
 | |
|   my_decimal.h from sql_string.h and sql_string.cc, which is not desirable.
 | |
| */
 | |
| bool
 | |
| str_set_decimal(uint mask, const my_decimal *val,
 | |
|                 uint fixed_prec, uint fixed_dec, char filler,
 | |
|                 String *str, CHARSET_INFO *cs)
 | |
| {
 | |
|   if (!(cs->state & MY_CS_NONASCII))
 | |
|   {
 | |
|     // For ASCII-compatible character sets we can use to_string_native()
 | |
|     val->to_string_native(str, fixed_prec, fixed_dec, filler, mask);
 | |
|     str->set_charset(cs);
 | |
|     return FALSE;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       For ASCII-incompatible character sets (like UCS2) we
 | |
|       call my_string_native() on a temporary buffer first,
 | |
|       and then convert the result to the target character
 | |
|       with help of str->copy().
 | |
|     */
 | |
|     uint errors;
 | |
|     StringBuffer<DECIMAL_MAX_STR_LENGTH> tmp;
 | |
|     val->to_string_native(&tmp, fixed_prec, fixed_dec, filler, mask);
 | |
|     return str->copy(tmp.ptr(), tmp.length(), &my_charset_latin1, cs, &errors);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Convert from decimal to binary representation
 | |
| 
 | |
|   SYNOPSIS
 | |
|     to_binary()
 | |
|     mask        error processing mask
 | |
|     d           number for conversion
 | |
|     bin         pointer to buffer where to write result
 | |
|     prec        overall number of decimal digits
 | |
|     scale       number of decimal digits after decimal point
 | |
| 
 | |
|   NOTE
 | |
|     Before conversion we round number if it need but produce truncation
 | |
|     error in this case
 | |
| 
 | |
|   RETURN
 | |
|     E_DEC_OK
 | |
|     E_DEC_TRUNCATED
 | |
|     E_DEC_OVERFLOW
 | |
| */
 | |
| 
 | |
| int my_decimal::to_binary(uchar *bin, int prec, decimal_digits_t scale,
 | |
|                           uint mask) const
 | |
| {
 | |
|   int err1= E_DEC_OK, err2;
 | |
|   my_decimal rounded;
 | |
|   my_decimal2decimal(this, &rounded);
 | |
|   rounded.frac= decimal_actual_fraction(&rounded);
 | |
|   if (scale < rounded.frac)
 | |
|   {
 | |
|     err1= E_DEC_TRUNCATED;
 | |
|     /* decimal_round can return only E_DEC_TRUNCATED */
 | |
|     decimal_round(&rounded, &rounded, scale, HALF_UP);
 | |
|   }
 | |
|   err2= decimal2bin(&rounded, bin, prec, scale);
 | |
|   if (!err2)
 | |
|     err2= err1;
 | |
|   return check_result(mask, err2);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Convert string for decimal when string can be in some multibyte charset
 | |
| 
 | |
|   SYNOPSIS
 | |
|     str2my_decimal()
 | |
|     mask            error processing mask
 | |
|     from            string to process
 | |
|     length          length of given string
 | |
|     charset         charset of given string
 | |
|     decimal_value   buffer for result storing
 | |
| 
 | |
|   RESULT
 | |
|     E_DEC_OK
 | |
|     E_DEC_TRUNCATED
 | |
|     E_DEC_OVERFLOW
 | |
|     E_DEC_BAD_NUM
 | |
|     E_DEC_OOM
 | |
| */
 | |
| 
 | |
| int str2my_decimal(uint mask, const char *from, size_t length,
 | |
|                    CHARSET_INFO *charset, my_decimal *decimal_value,
 | |
|                    const char **end_ptr)
 | |
| {
 | |
|   int err;
 | |
|   if (charset->mbminlen > 1)
 | |
|   {
 | |
|     StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp;
 | |
|     uint dummy_errors;
 | |
|     tmp.copy(from, length, charset, &my_charset_latin1, &dummy_errors);
 | |
|     char *end= (char*) tmp.end();
 | |
|     err= string2decimal(tmp.ptr(), (decimal_t*) decimal_value, &end);
 | |
|     *end_ptr= from + charset->mbminlen * (size_t) (end - tmp.ptr());
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     char *end= (char*) from + length;
 | |
|     err= string2decimal(from, (decimal_t*) decimal_value, &end);
 | |
|     *end_ptr= end;
 | |
|   }
 | |
|   check_result_and_overflow(mask, err, decimal_value);
 | |
|   return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   converts a decimal into a pair of integers - for integer and fractional parts
 | |
| 
 | |
|   special version, for decimals representing number of seconds.
 | |
|   integer part cannot be larger that 1e18 (otherwise it's an overflow).
 | |
|   fractional part is microseconds.
 | |
| */
 | |
| bool my_decimal2seconds(const my_decimal *d, ulonglong *sec,
 | |
|                         ulong *microsec, ulong *nanosec)
 | |
| {
 | |
|   int pos;
 | |
|   
 | |
|   if (d->intg)
 | |
|   {
 | |
|     pos= (d->intg-1)/DIG_PER_DEC1;
 | |
|     *sec= d->buf[pos];
 | |
|     if (pos > 0)
 | |
|       *sec+= static_cast<longlong>(d->buf[pos-1]) * DIG_BASE;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     *sec=0;
 | |
|     pos= -1;
 | |
|   }
 | |
| 
 | |
|   *microsec= d->frac ? static_cast<longlong>(d->buf[pos+1]) / (DIG_BASE/1000000) : 0;
 | |
|   *nanosec=  d->frac ? static_cast<longlong>(d->buf[pos+1]) % (DIG_BASE/1000000) : 0;
 | |
| 
 | |
|   if (pos > 1)
 | |
|   {
 | |
|     for (int i=0; i < pos-1; i++)
 | |
|       if (d->buf[i])
 | |
|       {
 | |
|         *sec= LONGLONG_MAX;
 | |
|         break;
 | |
|       }
 | |
|   }
 | |
|   return d->sign();
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   converts a pair of integers (seconds, microseconds) into a decimal
 | |
| */
 | |
| my_decimal *seconds2my_decimal(bool sign,
 | |
|                                ulonglong sec, ulong microsec, my_decimal *d)
 | |
| {
 | |
|   d->init();
 | |
|   longlong2decimal(sec, d); // cannot fail
 | |
|   if (microsec)
 | |
|   {
 | |
|     d->buf[(d->intg-1) / DIG_PER_DEC1 + 1]= microsec * (DIG_BASE/1000000);
 | |
|     d->frac= 6;
 | |
|   }
 | |
|   ((decimal_t *)d)->sign= sign;
 | |
|   return d;
 | |
| }
 | |
| 
 | |
| 
 | |
| my_decimal *date2my_decimal(const MYSQL_TIME *ltime, my_decimal *dec)
 | |
| {
 | |
|   longlong date= (ltime->year*100L + ltime->month)*100L + ltime->day;
 | |
|   if (ltime->time_type > MYSQL_TIMESTAMP_DATE)
 | |
|     date= ((date*100L + ltime->hour)*100L+ ltime->minute)*100L + ltime->second;
 | |
|   return seconds2my_decimal(ltime->neg, date, ltime->second_part, dec);
 | |
| }
 | |
| 
 | |
| 
 | |
| void my_decimal_trim(ulonglong *precision, decimal_digits_t *scale)
 | |
| {
 | |
|   if (!(*precision) && !(*scale))
 | |
|   {
 | |
|     *precision= 10;
 | |
|     *scale= 0;
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Convert a decimal to an ulong with a descriptive error message
 | |
| */
 | |
| 
 | |
| int my_decimal2int(uint mask, const decimal_t *d, bool unsigned_flag,
 | |
| 		   longlong *l, decimal_round_mode round_type)
 | |
| {
 | |
|   int res;
 | |
|   my_decimal rounded;
 | |
|   /* decimal_round can return only E_DEC_TRUNCATED */
 | |
|   decimal_round(d, &rounded, 0, round_type);
 | |
|   res= (unsigned_flag ?
 | |
|         decimal2ulonglong(&rounded, (ulonglong *) l) :
 | |
|         decimal2longlong(&rounded, l));
 | |
|   if (res & mask)
 | |
|   {
 | |
|     char buff[DECIMAL_MAX_STR_LENGTH];
 | |
|     int length= sizeof(buff);
 | |
|     decimal2string(d, buff, &length, 0, 0, 0);
 | |
| 
 | |
|     decimal_operation_results(res, buff,
 | |
|                               unsigned_flag ? "UNSIGNED INT" :
 | |
|                               "INT");
 | |
|   }
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| longlong my_decimal::to_longlong(bool unsigned_flag) const
 | |
| {
 | |
|   longlong result;
 | |
|   my_decimal2int(E_DEC_FATAL_ERROR, this, unsigned_flag, &result);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| my_decimal::my_decimal(Field *field)
 | |
| {
 | |
|   init();
 | |
|   DBUG_ASSERT(!field->is_null());
 | |
| #ifdef DBUG_ASSERT_EXISTS
 | |
|   my_decimal *dec=
 | |
| #endif
 | |
|   field->val_decimal(this);
 | |
|   DBUG_ASSERT(dec == this);
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifndef DBUG_OFF
 | |
| /* routines for debugging print */
 | |
| 
 | |
| /* print decimal */
 | |
| void
 | |
| print_decimal(const my_decimal *dec)
 | |
| {
 | |
|   int i, end;
 | |
|   char buff[512], *pos;
 | |
|   pos= buff;
 | |
|   pos+= sprintf(buff, "Decimal: sign: %d  intg: %d  frac: %d  { ",
 | |
|                 dec->sign(), dec->intg, dec->frac);
 | |
|   end= ROUND_UP(dec->frac)+ROUND_UP(dec->intg)-1;
 | |
|   for (i=0; i < end; i++)
 | |
|     pos+= sprintf(pos, "%09d, ", dec->buf[i]);
 | |
|   pos+= sprintf(pos, "%09d }\n", dec->buf[i]);
 | |
|   fputs(buff, DBUG_FILE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* print decimal with its binary representation */
 | |
| void
 | |
| print_decimal_buff(const my_decimal *dec, const uchar* ptr, int length)
 | |
| {
 | |
|   print_decimal(dec);
 | |
|   fprintf(DBUG_FILE, "Record: ");
 | |
|   for (int i= 0; i < length; i++)
 | |
|   {
 | |
|     fprintf(DBUG_FILE, "%02X ", (uint)((uchar *)ptr)[i]);
 | |
|   }
 | |
|   fprintf(DBUG_FILE, "\n");
 | |
| }
 | |
| 
 | |
| 
 | |
| const char *dbug_decimal_as_string(char *buff, const my_decimal *val)
 | |
| {
 | |
|   int length= DECIMAL_MAX_STR_LENGTH + 1;     /* minimum size for buff */
 | |
|   if (!val)
 | |
|     return "NULL";
 | |
|   (void)decimal2string((decimal_t*) val, buff, &length, 0,0,0);
 | |
|   return buff;
 | |
| }
 | |
| 
 | |
| 
 | |
| #endif /*DBUG_OFF*/
 | |
| #endif /*MYSQL_CLIENT*/
 | 
