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:
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user