mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +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*/
 |