TIDB源码分析-分析每一次PR(7)

从这篇开始跟随commit记录来分析每一次的PR,来继续加深对tidb的理解。

这里以每次merge来整体分析。

#1

这次合并包含了一次commit:

Update README.md

这次commit只是修改了一下README.md。就不做什么分析了。

#3

这是第二个被合并进来的PR,也只包含了一次commit:

store: update boltdb package import.

修复了之前引用了另外一个没包含进来的bolt包(在这之前其实没法正常编译的)。

#2

这个PR包含2个commit,不过都是针对gitignore的的修改。

parser: add gitignore

*: unify gitignore

这个也没什么好说的。

#4

这个PR包含一个commit,修改了Read.me的描述。

Improvement Documentation: various corrections of the English usage in readme

这个commit修改了一些描述的英语语法。

#5

这个PR也只有一次commit,

fix CurrentTimestamp check error for different timezones

修改了expression/expressions/helper_test.go中1970-01-01 08:20:34“由time.Unix(1234, 0).Format(mysql.TimeFormat)生成,并非直接写结果字符串,以修复在不同时区的带来的时差。

在test的检查函数里面找到原因:

		v, err := GetTimeValue(ctx, t.Expr, mysql.TypeTimestamp, mysql.MinFsp)
		c.Assert(err, IsNil)

		switch x := v.(type) {
		case mysql.Time:
			c.Assert(x.String(), DeepEquals, t.Ret)
		default:
			c.Assert(x, DeepEquals, t.Ret)
		}
	}

https://github.com/pingcap/tidb/blob/63ddb9c5312842e024cba0654794631b397b42de/expression/expressions/helper_test.go#L175-L184

进入GetTimeValue:

// GetTimeValue gets the time value with type tp.
func GetTimeValue(ctx context.Context, v interface{}, tp byte, fsp int) (interface{}, error) {
	return getTimeValue(ctx, v, tp, fsp)
}

func getTimeValue(ctx context.Context, v interface{}, tp byte, fsp int) (interface{}, error) {
	value := mysql.Time{
		Type: tp,
		Fsp:  fsp,
	}

	defaultTime, err := getSystemTimestamp(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}

	switch x := v.(type) {
	case string:
		if x == CurrentTimestamp {
			value.Time = defaultTime
		} else if x == ZeroTimestamp {
			value, _ = mysql.ParseTimeFromNum(0, tp, fsp)
		} else {
			value, err = mysql.ParseTime(x, tp, fsp)
			if err != nil {
				return nil, errors.Trace(err)
			}
		}

https://github.com/pingcap/tidb/blob/0d6f270068e8ff2aedc1c314e907771b6a508ebd/expression/expressions/helper.go#L381-L408

这里传入的是string类型的CURRENT_TIMESTAMP,所以从getSystemTimestamp获得defaultTime后直接赋值给了结果value,然后进入getSystemTimestamp看一下:

func getSystemTimestamp(ctx context.Context) (time.Time, error) {
	value := time.Now()

	if ctx == nil {
		return value, nil
	}

	// check whether use timestamp varibale
	sessionVars := variable.GetSessionVars(ctx)
	if v, ok := sessionVars.Systems["timestamp"]; ok {
		if v != "" {
			timestamp, err := strconv.ParseInt(v, 10, 64)
			if err != nil {
				return time.Time{}, errors.Trace(err)
			}

			if timestamp <= 0 {
				return value, nil
			}

			return time.Unix(timestamp, 0), nil
		}
	}

	return value, nil
}

https://github.com/pingcap/tidb/blob/0d6f270068e8ff2aedc1c314e907771b6a508ebd/expression/expressions/helper.go#L354-L379

这边判断了传入的ctx的SessionVars的map的timestamp值,而之前已经给这个值设置了”1234″:

sessionVars.Systems["timestamp"] = "1234"

	tbl := []struct {
		Expr interface{}
		Ret  interface{}
	}{
		{"2012-12-12 00:00:00", "2012-12-12 00:00:00"},
		{CurrentTimestamp, time.Unix(1234, 0).Format(mysql.TimeFormat)},
		{ZeroTimestamp, "0000-00-00 00:00:00"},
		{Value{"2012-12-12 00:00:00"}, "2012-12-12 00:00:00"},
		{Value{int64(0)}, "0000-00-00 00:00:00"},
		{Value{}, nil},
		{CurrentTimeExpr, CurrentTimestamp},
		{NewUnaryOperation(opcode.Minus, Value{int64(0)}), "0000-00-00 00:00:00"},
		{mockExpr{}, nil},
	}

https://github.com/pingcap/tidb/blob/63ddb9c5312842e024cba0654794631b397b42de/expression/expressions/helper_test.go#L157-L172

所以会返回return time.Unix(timestamp, 0), nil。和最新的修改的time.Unix(1234, 0).Format(mysql.TimeFormat)所匹配,如果还是之前的固定的时间”1970-01-01 08:20:34“的话,不在东八区运行的话时间就不是这个了。

Fix mysqldef test(#6)

这个PR包含两个commit:

remove wrong test

min/max datetime use local location:

这两个commit需要一起看,首先在第一个commit删除的那行测试数据下面的测试函数:

	for _, test := range table {
		// test ParseDatetimeFromNum
		t, err := ParseDatetimeFromNum(test.Input)
		if test.ExpectDateTimeError {
			c.Assert(err, NotNil)
		} else {
			c.Assert(err, IsNil)
			c.Assert(t.Type, Equals, TypeDatetime)
		}
		c.Assert(t.String(), Equals, test.ExpectDateTimeValue)

		// test ParseTimestampFromNum
		t, err = ParseTimestampFromNum(test.Input)
		if test.ExpectTimeStampError {
			c.Assert(err, NotNil)
		} else {
			c.Assert(err, IsNil)
			c.Assert(t.Type, Equals, TypeTimestamp)
		}
		c.Assert(t.String(), Equals, test.ExpectTimeStampValue)

		// test ParseDateFromNum
		t, err = ParseDateFromNum(test.Input)

		if test.ExpectDateTimeError {
			c.Assert(err, NotNil)
		} else {
			c.Assert(err, IsNil)
			c.Assert(t.Type, Equals, TypeDate)
		}
		c.Assert(t.String(), Equals, test.ExpectDateValue)
	}
}

https://github.com/pingcap/tidb/blob/7f064ddfe2b3a21b715c83b898ba322a42d96098/mysqldef/time_test.go#L378-L410

进入ParseDatetimeFromNum:

// ParseDatetimeFromNum is a helper function wrapping ParseTimeFromNum with datetime type and default fsp.
func ParseDatetimeFromNum(num int64) (Time, error) {
	return ParseTimeFromNum(num, TypeDatetime, DefaultFsp)
}

https://github.com/pingcap/tidb/blob/18140c6effefa2cae755edaaa7887311ffb951e7/mysqldef/time.go#L936-L939

继续进入:

// ParseTimeFromNum parses a formatted int64,
// returns the value which type is tp.
func ParseTimeFromNum(num int64, tp byte, fsp int) (Time, error) {
	fsp, err := checkFsp(fsp)
	if err != nil {
		return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
	}

	t, err := parseDateTimeFromNum(num)
	if err != nil {
		return Time{Time: ZeroTime, Type: tp}, errors.Trace(err)
	}

	if !checkDatetime(t) {
		return Time{Time: ZeroTime, Type: tp}, ErrInvalidTimeFormat
	}

	t.Fsp = fsp
	return t.Convert(tp)
}

https://github.com/pingcap/tidb/blob/18140c6effefa2cae755edaaa7887311ffb951e7/mysqldef/time.go#L915-L934

这里面有个checkDatetime()来检查合法性:

func checkDatetime(t Time) bool {
	if t.IsZero() {
		return true
	}

	if t.Time.After(MaxDatetime) || t.Time.Before(MinDatetime) {
		return false
	}

	return true
}

https://github.com/pingcap/tidb/blob/18140c6effefa2cae755edaaa7887311ffb951e7/mysqldef/time.go#L952-L962

这里用t.Time.After(MaxDatetime) || t.Time.Before(MinDatetime)检查了是否超过最大最小范围,而MaxDatetime和MinDatetime的定义为:

	// MinDatetime is the minimum for mysql datetime type.
	MinDatetime = time.Date(1000, 1, 1, 0, 0, 0, 0, time.Local)
	// MaxDatetime is the maximum for mysql datetime type.
	MaxDatetime = time.Date(9999, 12, 31, 23, 59, 59, 999999, time.Local)

https://github.com/pingcap/tidb/blob/18140c6effefa2cae755edaaa7887311ffb951e7/mysqldef/time.go#L90-L94

而由于t.Time.After(MaxDatetime)和t.Time.Before(MinDatetime)都是用时间戳比较的,而t是在这里生成的:

	return time.Date(year, time.Month(month), day, hour, minute, second, frac*1000, time.Local), nil
}

https://github.com/pingcap/tidb/blob/18140c6effefa2cae755edaaa7887311ffb951e7/mysqldef/time.go#L454-L455

所以必须统一时区。就有了第二个commit的把UTC时区改成了Local。

tools: add .travis.yml(#7)

这个PR增加了travis ci的脚本,也不必要多说了。

Update README.md(#9)

此PR在README.md增加了travis的构建图标。

types: Improve Convert function(#8)

这个PR把这段代码实现的功能写到了Convert函数:

	if f.Tp.Tp == mysql.TypeString && f.Tp.Charset == charset.CharsetBin {
		nv = []byte(nv.(string))
	}
	if f.Tp.Flen != types.UnspecifiedLength {
		switch f.Tp.Tp {
		case mysql.TypeString:
			v := nv.(string)
			if len(v) > int(f.Tp.Flen) {
				v = v[:f.Tp.Flen]
			}
			return v, nil
		}
	}
	if f.Tp.Tp == mysql.TypeLonglong {
		if mysql.HasUnsignedFlag(f.Tp.Flag) {
			return uint64(nv.(int64)), nil
		}
	}

然后:

if f.Tp.Flen != types.UnspecifiedLength {
		switch f.Tp.Tp {
		case mysql.TypeString:
			v := nv.(string)
			if len(v) > int(f.Tp.Flen) {
				v = v[:f.Tp.Flen]
			}
			return v, nil
		}
	}

这段功能已经包含在:

x = truncateStr(x, target.Flen)

最后加上对应的测试数据。

tools: use go1.5 for CI(#11)

这个PR把CI的go版本升级到了1.5

init stores when declare(#13)

这个PR谢大合并了stores = make(map[string]kv.Driver)的申明方式。

*: Remove conversion function(#14)

删除了conversion (并不是CONVERT)函数以及相关测试数据等,申砾说在mysql中没有用到这个。

util/types: support convert year(#10)

给Convert函数增加支持转换val值到mysql.TypeYear(是个int16)。

暂时先看这么多,今天写到这里啦。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

请输入正确的验证码