parser,plan,ast: fix #4239, concatenates string literals which placed each other, and use first string as projection name (#4252)

This commit is contained in:
winkyao
2017-08-21 12:37:27 +08:00
committed by GitHub
parent acff4b60ec
commit fce58065d7
6 changed files with 133 additions and 23 deletions

View File

@ -52,6 +52,7 @@ var (
// ValueExpr is the simple value expression.
type ValueExpr struct {
exprNode
projectionOffset int
}
// NewValueExpr creates a ValueExpr with value, and sets default field type.
@ -62,9 +63,20 @@ func NewValueExpr(value interface{}) *ValueExpr {
ve := &ValueExpr{}
ve.SetValue(value)
types.DefaultTypeForValue(value, &ve.Type)
ve.projectionOffset = -1
return ve
}
// SetProjectionOffset sets ValueExpr.projectionOffset for logical plan builder.
func (n *ValueExpr) SetProjectionOffset(offset int) {
n.projectionOffset = offset
}
// GetProjectionOffset returns ValueExpr.projectionOffset.
func (n *ValueExpr) GetProjectionOffset() int {
return n.projectionOffset
}
// Accept implements Node interface.
func (n *ValueExpr) Accept(v Visitor) (Node, bool) {
newNode, skipChildren := v.Enter(n)

View File

@ -462,6 +462,77 @@ func (s *testSuite) TestSelectStringLiteral(c *C) {
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "abc 123 ")
// Issue #4239.
sql = `select 'a' ' ' 'string';`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("a string"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "a")
sql = `select 'a' " " "string";`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("a string"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "a")
sql = `select 'string' 'string';`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("stringstring"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "string")
sql = `select "ss" "a";`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("ssa"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "ss")
sql = `select "ss" "a" "b";`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("ssab"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "ss")
sql = `select "ss" "a" ' ' "b";`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("ssa b"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "ss")
sql = `select "ss" "a" ' ' "b" ' ' "d";`
r = tk.MustQuery(sql)
r.Check(testkit.Rows("ssa b d"))
rs, err = tk.Exec(sql)
c.Check(err, IsNil)
fields, err = rs.Fields()
c.Check(err, IsNil)
c.Check(len(fields), Equals, 1)
c.Check(fields[0].Column.Name.O, Equals, "ss")
}
func (s *testSuite) TestSelectLimit(c *C) {

View File

@ -451,24 +451,7 @@ func scanQuotedIdent(s *Scanner) (tok int, pos Pos, lit string) {
}
func startString(s *Scanner) (tok int, pos Pos, lit string) {
tok, pos, lit = s.scanString()
if tok == unicode.ReplacementChar {
return
}
// Quoted strings placed next to each other are concatenated to a single string.
// See http://dev.mysql.com/doc/refman/5.7/en/string-literals.html
ch := s.skipWhitespace()
if s.sqlMode&mysql.ModeANSIQuotes > 0 &&
ch == '"' || s.r.s[pos.Offset] == '"' {
return
}
for ch == '\'' || ch == '"' {
_, _, lit1 := s.scanString()
lit = lit + lit1
ch = s.skipWhitespace()
}
return
return s.scanString()
}
// lazyBuf is used to avoid allocation if possible.

View File

@ -184,8 +184,6 @@ func (s *testLexerSuite) TestscanString(c *C) {
expect string
}{
{`' \n\tTest String'`, " \n\tTest String"},
{`'a' ' ' 'string'`, "a string"},
{`'a' " " "string"`, "a string"},
{`'\x\B'`, "xB"},
{`'\0\'\"\b\n\r\t\\'`, "\000'\"\b\n\r\t\\"},
{`'\Z'`, string(26)},
@ -315,7 +313,6 @@ func (s *testLexerSuite) TestSQLModeANSIQuotes(c *C) {
{"`identifier`", identifier, "identifier"},
{`"identifier""and"`, identifier, `identifier"and`},
{`'string''string'`, stringLit, "string'string"},
{`'string' 'string'`, stringLit, "stringstring"},
{`"identifier"'and'`, identifier, "identifier"},
{`'string'"identifier"`, stringLit, "string"},
}
@ -328,6 +325,14 @@ func (s *testLexerSuite) TestSQLModeANSIQuotes(c *C) {
c.Assert(tok, Equals, t.tok)
c.Assert(v.ident, Equals, t.ident)
}
scanner.reset(`'string' 'string'`)
var v yySymType
tok := scanner.Lex(&v)
c.Assert(tok, Equals, stringLit)
c.Assert(v.ident, Equals, "string")
tok = scanner.Lex(&v)
c.Assert(tok, Equals, stringLit)
c.Assert(v.ident, Equals, "string")
}
func (s *testLexerSuite) TestIllegal(c *C) {

View File

@ -752,6 +752,7 @@ import (
StatsPersistentVal "stats_persistent value"
StringName "string literal or identifier"
StringList "string list"
StringLiteral "text literal"
ExplainableStmt "explainable statement"
SubSelect "Sub Select"
Symbol "Constraint Symbol"
@ -891,6 +892,9 @@ import (
%precedence lowerThanIntervalKeyword
%precedence interval
%precedence lowerThanStringLitToken
%precedence stringLit
%precedence lowerThanSetKeyword
%precedence set
@ -2622,7 +2626,6 @@ Literal:
| floatLit
| decLit
| intLit
| stringLit
{
tp := types.NewFieldType(mysql.TypeString)
tp.Charset, tp.Collate = parser.charset, parser.collation
@ -2630,6 +2633,10 @@ Literal:
expr.SetType(tp)
$$ = expr
}
| StringLiteral %prec lowerThanStringLitToken
{
$$ = $1
}
| "UNDERSCORE_CHARSET" stringLit
{
// See https://dev.mysql.com/doc/refman/5.7/en/charset-literal.html
@ -2648,6 +2655,33 @@ Literal:
| hexLit
| bitLit
StringLiteral:
stringLit
{
tp := types.NewFieldType(mysql.TypeString)
tp.Charset, tp.Collate = parser.charset, parser.collation
expr := ast.NewValueExpr($1)
expr.SetType(tp)
$$ = expr
}
| StringLiteral stringLit
{
valExpr := $1.(*ast.ValueExpr)
strLit := valExpr.GetString()
tp := types.NewFieldType(mysql.TypeString)
tp.Charset, tp.Collate = parser.charset, parser.collation
expr := ast.NewValueExpr(strLit+$2)
// Fix #4239, use first string literal as projection name.
if valExpr.GetProjectionOffset() >= 0 {
expr.SetProjectionOffset(valExpr.GetProjectionOffset())
} else {
expr.SetProjectionOffset(len(strLit))
}
expr.SetType(tp)
$$ = expr
}
Operand:
Literal
{

View File

@ -437,9 +437,14 @@ func (b *planBuilder) buildProjectionFieldNameFromExpressions(field *ast.SelectF
// Literal: Need special processing
switch valueExpr.Kind() {
case types.KindString:
projName := valueExpr.GetString()
projOffset := valueExpr.GetProjectionOffset()
if projOffset >= 0 {
projName = projName[:projOffset]
}
// See #3686, #3994:
// For string literals, string content is used as column name. Non-graph initial characters are trimmed.
fieldName := strings.TrimLeftFunc(valueExpr.GetString(), func(r rune) bool {
fieldName := strings.TrimLeftFunc(projName, func(r rune) bool {
return !unicode.IsOneOf(mysql.RangeGraph, r)
})
return model.NewCIStr(fieldName)