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

从这篇开始,以后会在一篇文章里面写比较多的PR后再发布,不然文章数量太多了。

tables tests: replace cnt + 1 to cnt++ #106

使用cnt++替换cnt += 1

driver: check argument types #105

在checkArgs()增加对类型的转换,转换到mysql类型。

expressions: update subquery #110

对 subquery增加返回row()的处理以及支持plan。

parser/parser.y: S/R conflicts 10->9. #104

修复了一个S/R冲突。

check subquery column #111

对columnCount()支持subquery的计算,需要调用plan,所以传值增加了 ctx。

并且多select.go支持了checkOneColumn()检查是否只有一列数据。并在select的plan中的Fields,GroupBy,OrderBy,Having表达式增加这个检查(他们只允许返回一列的值)。

expressions,parser: IN expression uses subquery #113

对in表达式支持subquery子查询。然后每次检查表达式左右列数相等。

parser/parser.y: S/R conflicts 9->4. #116

解决一个S/R 冲突。

Shenli/show variables #114

支持类似SHOW VARIABLES LIKE ‘character_set_results’;的语法。

parser/parser.y: S/R conflicts 4->3. #118

解决一个S/R冲突。

parser/parser.y: S/R conflicts 3->2. #119

解决一个S/R冲突。

parser/parser.y: S/R conflicts 2->1. #121

解决一个S/R冲突。

add pre-requirement #115

README.md增加了go版本要求说明。

parser/parser.y: S/R conflicts 1->0. #122

解决了所有S/R冲突。

parser: Charset name can be string literal #117

对SET CHARACTER SET ‘utf8mb4’支持字符串。

kv,tidb: fix issue #67 #126

修复issue#67不同索引前缀可能被包含的问题。

以前索引key是使用indexPrefix_indexName来编码的,当两个索引名为c和c1时,由于indexPrefix都是相同的,参考indexprefix生成:

		indexPrefix:  fmt.Sprintf("%d_i", tableID),

https://github.com/pingcap/tidb/blob/b540a5a2fd5bdd98819bc86f78de824187f5db61/table/tables/tables.go#L82-L82

(同一个表的indexprefix都是相同的)

再根据索引前缀生成规则:

fmt.Sprintf("%s_%s", indexPrefix, indexName)

那么c和c1两个索引的key会变成1_i_c1和1_i_c,这样查询c索引的时候会把c1索引全部查询出来。

解决:改变编码规则,在最后加上0x00, 0x01这样前缀就区分开了。

Makefile: Make check by default. #127

makefile all中增加check。

types: Fix convert TypeBit error #131

修复bit类型转换时出错。

makefile, session: tiny clean up and fix a golint check. #132

清理和修复代码格式检查。

expressions: use integer for time in arithmetic op if FSP = 0 #133

当mysql.Time和mysql.Duration的FSP(精度)为0时则转换为整数。

kv: Remove unused fields #134

删除没有用到的字段。

column: Remove unused indexKey #135

删除未使用的indexKey。

Support any/some/all subquery. #125

新增支持any/some/all的子查询。

以select 1 < (select 1)为例,首先根据整个语句plan出来,然后在newdriverRows()中运行Do后会到达:

	p, err := cs.R.Plan(ctx)
	if err != nil {
		return nil, errors.Trace(err)
	}

https://github.com/pingcap/tidb/blob/002b2a3bf8be45b6a4526b08b9711cd3d8b24e0e/expression/expressions/cmp_subquery.go#L89-L94

这里会对子查询(select 1)进行plan:

// Plan implements plan.Planner interface.
func (sq *SubQuery) Plan(ctx context.Context) (plan.Plan, error) {
	if sq.p != nil {
		return sq.p, nil
	}

	var err error
	sq.p, err = sq.Stmt.Plan(ctx)
	return sq.p, errors.Trace(err)
}

https://github.com/pingcap/tidb/blob/af8d96dff316b21e9747328a4f5204016fd9f6a7/expression/expressions/subquery.go#L112-L121

然后返回后会在checkResult得到最终比较结果。

expression, parser: Tiny clean up #136

清理一些无效代码。

store, parser: Remove unused variables #137

删除没有使用的变量。

parser: Remove imaginary and runeType #138

清理代码。

check arithmetic operation overflow #140

对算术计算检查是否溢出。

use absolute path for sed on mac #142

makefie:在Mac上使用绝对路径.

Tiny clean up #141

清理代码。

Shenli/cleanup function #139

整理了各个函数。

parser: Fix bug in parse database() #147

修复类似select DATABASE()这样的语句错误。

parser: simplify compare operation #148

简化比较操作的parser代码。

arithmetic overflow #143

支持更多的计算溢出检查。

add make command shell #130

添加make.cmd的windows命令

parser: cleanup parser/scanner error. #145

修改parser和scanner中的错误输出提示。

plans: Support information_schema.collations #150

给mysql内置数据库information_schema增加表collations的数据。

parser: Support cast char with charset #151

支持类似SELECT *, CAST(data AS CHAR CHARACTER SET utf8) FROM t;这样的字符集转换 语法。

*: Support binary operator #153

支持mysql的BINARY表达式:https://dev.mysql.com/doc/refman/5.7/en/cast-functions.html#operator_binary

合并在了FunctionCast,加了表示变量,其实SELECT ‘a’ = ‘A’;这样的sql在mysql返回的是1(忽略大小写的),而SELECT BINARY ‘a’ = ‘A’;才返回0。

plan: Next skeleton #123

这个PR比较大,包含很多commit,对Plan支持Next()。

一个一个commit来分析:

plan: Next skeleton:先对需要用到next的plan添加空的plan()函数。

plan: Next return Row type:使用plan.Row{}替换Next的data []interface{}, rowKeys []*plan.RowKeyEntr (其实就是放到了一个结构体)

plan: add Close method:增加Close()来关闭Next(),都还未实现。

plan: call Next if plan Implemented Next.:给Plan接口新增了ImplementedNext(),会不断调用有ImplementedNext方法的Plan,用于检测是否要有下一个Next()。接着在Recordset的Do()中添加了对Next()的调用。

plans: Implement next for simple table selection.:对之前的ImplementedNext改名成ImplementedNext ,实现了部分Plan的Next(),其实就是用Next()重构了原来的Do()。

plans: implement FilterDefaultPlan and modify some tests.:把where的Do也写到了Next()。增加了一些测试数据。

plan: Implement Next for limit and offset plan.:实现了limit和offset的Next()。

plan: Implement Next for order by plan:实现了order by的Next()。

plan: implement Next for TableNilPlan:实现了TableNil的Next()。

plan: implement DistinctDefaultPlan and modified some tests:实现了DistinctDefaultPlan的Next()。

plan: implement Next for index plan:实现了index的Next()。

plan: implement Next for inforschema, fix import for errors.:实现了InfoSchemaPlan的Next()。

plan: implement Next fort ShowPlan:实现了ShowPlan的Next()。

plan: show fix golint check:修改变量名通过golint检查。

plan: make plan reusable after close:在colose()清理一些变量,使其回到初始值,以支持close后的plan可以重复使用,执行Next()等。

plan: implement Next for GroupByDefaultPlan:实现了GroupByDefault的Next()。

plan: implement Next for HavingPlan:实现了Having的Next()。

plan: implement Next for UnionPlan:实现了Union的Next()。

plan: Implement Next for JoinPlan:实现了Join的Next()。

plan: cleanup, remove NextPlan interface.:删除了之前添加的UseNext().

plan: address comments.:修改注释。

plan: adjust left right cross join:修改 cross join左右儿子遍历顺序。

plan: fix interface comments:修改了注释。

plan: address comments:增加注释,清理了一些语法。

plan: fix infoschema:补上没有添加到Next中的fetchCollations。

types: Allow []byte as orderby type #154

增加[]byte类型到Isorderby(),标记可以作为排序类型。

plans: Fix build error #156

在fetchAll也修改IsOrderedType()的调用方式。

support hexadecimal literal type #157

支持16进制https://dev.mysql.com/doc/refman/5.7/en/hexadecimal-literals.html 运算。(这里还未支持select 的打印输出)

*: Clean up unused code #152

清理无效代码。

types: remove unnecessary code #160

删除没有用到的代码。

expressions,tidb: unary handes bool type #161

修复select + (1 > 0), – (1 > 0)的错误。

fix issue 108 #146

修复类似UPDATE user T0 LEFT OUTER JOIN user_profile T1 ON T1.id = T0.profile_id SET T0.profile_id = ? WHERE T0.profile_id IN (?);的错误。

也就是支持了带有join的修改语句。

Add exists subquery support. #144

支持exists的子查询语句。

driver: add Next method for Recordset and use it in driver#162

把plan中的Do()全部删掉,替换成新的Next()。

tables: Allow multiple null entries for unique index column#163

解决unique索引不能插入多个NULL值的问题。

drop table if exists t;
CREATE TABLE test.t (a int primary key auto_increment, b varchar(255) unique);
insert into t values (1, NULL), (2,NULL);

原因:

在Creat()中发现了已经存在的KEY:

	// unique index
	_, err = txn.Get(keyBuf)
	if IsErrNotFound(err) {
		err = txn.Set(keyBuf, encodeHandle(h))
		return errors.Trace(err)
	}

	return errors.Trace(ErrKeyExists)
}

https://github.com/pingcap/tidb/blob/cdd2bc49e7209e34d5338f4ee985dd81964bcf79/kv/index_iter.go#L156-L164

解决:

需要让unique时如果为NULL,那么可以无限插入,则需要保证keyBuf不同。那么keyBuf是在这里获得的:

// Create creates a new entry in the kvIndex data.
// If the index is unique and there already exists an entry with the same key, Create will return ErrKeyExists
func (c *kvIndex) Create(txn Transaction, indexedValues []interface{}, h int64) error {
	keyBuf, err := c.genIndexKey(indexedValues, h)
	if err != nil {
		return err
	}

https://github.com/pingcap/tidb/blob/cdd2bc49e7209e34d5338f4ee985dd81964bcf79/kv/index_iter.go#L143-L149

所以进入genIndexKey,保证key不重复即可。

修改后的genIndexKey:

func (c *kvIndex) genIndexKey(indexedValues []interface{}, h int64) ([]byte, error) {
	var (
		encVal []byte
		err    error
	)
	// only support single value index
	if !c.unique {
		encVal, err = EncodeValue(append(indexedValues, h)...)
	} else {
		/*
			See: https://dev.mysql.com/doc/refman/5.7/en/create-index.html
			A UNIQUE index creates a constraint such that all values in the index must be distinct.
			An error occurs if you try to add a new row with a key value that matches an existing row.
			For all engines, a UNIQUE index permits multiple NULL values for columns that can contain NULL.
		*/
		containsNull := false
		for _, cv := range indexedValues {
			if cv == nil {
				containsNull = true
			}
		}
		if containsNull {
			encVal, err = EncodeValue(append(indexedValues, h)...)
		} else {
			encVal, err = EncodeValue(indexedValues...)
		}
	}
	if err != nil {
		return nil, err
	}
	buf := append([]byte(nil), []byte(c.prefix)...)
	buf = append(buf, encVal...)
	return buf, nil
}

https://github.com/pingcap/tidb/blob/b92da5d3f04f760b4e17004d886cd0369526b7f7/kv/index_iter.go#L124-L157

tidb: Clean up tidb test #167

清理测试数据。

Set ServerStatusInTrans flag #159

把以前的IsAutocommit()的修改为ShouldAutocommit(),并且增加SetStatusInTrans()设置标志位。

stmts: Fix bug in update single-table syntax but with multiple tables #168

修复下列SQL执行错误:

drop table if exists items;
drop table if exists month;
CREATE TABLE items (id int, price TEXT);
insert into items values (11, "items_price_11"), (12, "items_price_12"), (13, "items_price_13");
CREATE TABLE month (mid int, mprice TEXT);
insert into month values (11, "month_price_11"), (22, "month_price_22"), (13, "month_price_13");
UPDATE items join month on items.id=month.mid SET items.price=month.mid;

原因:

			tcols, err2 := getUpdateColumns(tbl, s.List, s.MultipleTable)
			if err2 != nil {
				return nil, errors.Trace(err2)
			}
			if len(tcols) == 0 {
				// Nothing to update for this table.
				continue
			}
			// Get data in the table
			err2 = updateRecord(ctx, handle, data, tbl, tcols, s.List, nil, m)
			if err2 != nil {
				return nil, errors.Trace(err2)
			}
			updatedRowKeys[k] = true
		}
	}
	return nil, nil
}

https://github.com/pingcap/tidb/blob/116098baf982f5712ac3d6968223798dea8492d5/stmt/stmts/update.go#L280-L297

进入这里时s.MultipleTable还是false,那么在getUpdateColumns()找不到另外一张表的字段,所以抛出了错误。

解决:

给JoinRset加上了判断是否有join,有就表示存在多张表:

// MultipleTable checks if the JoinRset contains multiple tables.
func (r *JoinRset) MultipleTable() bool {
	if _, ok := r.Left.(*TableSource); ok {
		if r.Right == nil {
			return false
		}
	}
	return true
}

https://github.com/pingcap/tidb/blob/6ec054f4462a9b0bd0007e882cb9dfdfdea5a5a8/rset/rsets/join.go#L235-L243

stmts: Rename id to handle #171

修改变量名。

*: Replace h with handle #172

把行号变量h改名为handle。

codec: fix ARM build fail #169

修复在arm等32位机器上编译错误。把超出uint32的值加上强制转换uint64即可(int同理)

 

发表评论

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

请输入正确的验证码