mysql-字符串转换成数字的源码解析

一、问题现象

mysql> select 0='abc';
+---------+
| 0='abc' |
+---------+
|       1 |
+---------+

二、排查原因

今天开发找我说了个查询现象,0值和字符串相比竟然相等,以前没有遇到过该现象,所以排查了下
重点: 数据库开发规范中一定要要求等值对比或者其他运算的时候一定要要求数据类型一致

mysql> select cast('abc' as signed);
+-----------------------+
| cast('abc' as signed) |
+-----------------------+
|                     0 |
+-----------------------+
1 row in set, 1 warning (0.27 sec)

mysql> select cast('1abc' as signed);
+------------------------+
| cast('1abc' as signed) |
+------------------------+
|                      1 |
+------------------------+
1 row in set, 1 warning (0.27 sec)

mysql> select cast('abc1' as signed);
+------------------------+
| cast('abc1' as signed) |
+------------------------+
|                      0 |
+------------------------+
1 row in set, 1 warning (0.27 sec)

mysql> select cast('23abc1' as signed);
+--------------------------+
| cast('23abc1' as signed) |
+--------------------------+
|                       23 |
+--------------------------+
1 row in set, 1 warning (0.27 sec)

mysql> SELECT CONV('19', 10, 2);
+-----------------+
| CONV(19, 10, 2) |
+-----------------+
| 10011           |
+-----------------+
1 row in set (0.00 sec)

mysql> SELECT CONV('19', 10, 37);
+------------------+
| CONV(19, 10, 37) |
+------------------+
| NULL             |
+------------------+
1 row in set (0.00 sec)

我们可以从上面的实验看到,只要字符串最左的一位不是数值的话,整体字符串就会变为0值,所以在对比的时候一定要注意数据类型一致,具体内容可以看官网文章

三、源码解析

文件位置:strings/str2int.c

1.源码分析

#define char_val(X) (X >= '0' && X <= '9' ? X-'0' :\
		     X >= 'A' && X <= 'Z' ? X-'A'+10 :\
		     X >= 'a' && X <= 'z' ? X-'a'+10 :\
		     '\177')

// 函数的输入参数包括源字符串指针(src),进制(radix),最小值(lower)和最大值(upper),以及一个指向long int类型变量的指针(val),用于存储转换后的整数值
char *str2int(const char *src, int radix, long int lower,
	      long int upper, long int *val)
{
  int sign;			/* 正负数标识 */
  int n;			/* 尚未转换的位数 */
  long limit;			/* 最大可能的输入值 */
  long scale;			/* 下一个数字乘以的数量 */
  long sofar;			/* 当前运行的值 */
  int d;		
  char *start;
  int digits[32];		/* 数字存储的数组 */

  /*  如果检测到错误,结果将为 NullS,放入val中的数值为0  */

  *val = 0;

  /* 如果进制不在2到36之间就返回空指针并报错,这里也能解释 SELECT CONV('19', 10, 37);返回为null的情况  */

#ifndef NDEBUG
  if (radix < 2 || radix > 36) {
    errno=EDOM;
    return NullS;
  }
#endif
  /* 如果lower是+,limit为-或0,limit为min(scale,limit)  */

  if ((limit = lower) > 0) limit = -limit;
  if ((scale = upper) > 0) scale = -scale;
  if (scale < limit) limit = scale;

 //利用while循环来跳过前置空格,遇到第一个非空格停止循环
  while (my_isspace(&my_charset_latin1,*src)) src++;
  sign = -1;
  // 这里也能验证出select 3="+3"为true的现象
  if (*src == '+') src++; else
    if (*src == '-') src++, sign = 1;

  start=(char*) src;
  // 如果遍历的当前字符串是0就继续往下循环,所以也能证明select 2='000002'返回是1的情况
  while (*src == '0') src++;

  // 通过循环读取字符并将其转换成对应的数值,存储在digits数组中,因为bigint的数值范围的关系,所以这里面的n设置为20即可,以字符串'19'为例,此时得到的digest为{1,9},n为2
  for (n = 0; (digits[n]=char_val(*src)) < radix && n < 20; n++,src++) ;

  /*  如果转换后和转换前的字符一样则表示没有一个数字,返回Nulls,这里也能解释为什么select 0='abc' 返回是1的情况了  */

  if (start == src) {
    errno=EDOM;
    return NullS;
  }

  // 遍历digits数组并按照指定进制进行转换,递减循环,scale = -radix**k, and scale < sofar < 0,此时n=2,--n=1,-(d=digits[n]) 为-9,把limit看成-10,第二遍遍历的时候n=1,n--已经<1,n--后,n=0,所以退出循环
  for (sofar = 0, scale = -1; --n >= 1;)
  {
  // 检测数值是否在limit范围内
    if ((long) -(d=digits[n]) < limit) {
      errno=ERANGE;
      return NullS;
    }
    /* // limit = (-10+9)/10=-0.1,sofar=0+9*(-1)=-9; scale=-1*10=-10*/
    limit = (limit+d)/radix, sofar += d*scale; scale *= radix;
  }
  //查看最后一个数字,即最左侧的数字
  if (n == 0)
  {
    if ((long) -(d=digits[n]) < limit)	
    {
      errno=ERANGE;
      return NullS;
    }
    // sofar = -9 + 1 *(-10) = -19
    sofar+=d*scale;
  }

  // 如果是+数,sofar= -sofar赋值为相反数,所以这里输出的是19
  if (sign < 0)
  {
    if (sofar < -LONG_MAX || (sofar= -sofar) > upper)
    {
      errno=ERANGE;
      return NullS;
    }
  }
  
  else if (sofar < lower)
  {
    errno=ERANGE;
    return NullS;
  }
  *val = sofar;
  errno=0;			/* indicate that all went well */
  return (char*) src;
}

2.举例

# 1.把二进制字符串10011转换成10进制的数字
# for (sofar = 0, scale = -1; --n >= 1;)
# sofar += d*scale; scale *= radix;
digest={1,0,0,1,1},n=5
sofar=0+1*(-1)=-1; scale=-1*2=-2
sofar=-1+1*(-2)=-3; scale=-2*2=-4
sofar=-3+0*(-4)=-3; scale=-4*2=-8
sofar=-3+0*(-8)=-3; scale=-8*2=-16

# if (n==0) sofar+=d*scale;
sofar=-3+1*(-16)=-19
# if (sofar < -LONG_MAX || (sofar= -sofar) > upper)
sofar=-sofar=19


# 1.把十六进制字符串54C转换成10进制的数字
digest={5,4,'C'},n=3
sofar=0+12*(-1)=-12; scale=-1*16=-16
sofar=-12+4*(-16)=-76; scale=-16*16=-256

# if (n==0) sofar+=d*scale;
sofar=-76+5*(-256)=-1356

sofar=-sofar=1356

你可能感兴趣的:(mysql,数据库)