diff --git a/parser/scanner.l b/parser/scanner.l index 0364493921..82ed50740c 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -26,6 +26,7 @@ import ( "strings" "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/util/stringutil" mysql "github.com/pingcap/tidb/mysqldef" ) @@ -1011,7 +1012,8 @@ func (l *lexer) str(lval *yySymType, pref string) int { s = strings.TrimSuffix(s, "'") + "\"" pref = "\"" } - v, err := strconv.Unquote(pref + s) + v := stringutil.RemoveUselessBackslash(pref+s) + v, err := strconv.Unquote(v) if err != nil { v = strings.TrimSuffix(s, pref) } diff --git a/tidb_test.go b/tidb_test.go index aa082ab6a7..2764db7eae 100644 --- a/tidb_test.go +++ b/tidb_test.go @@ -882,6 +882,16 @@ func (s *testSessionSuite) TestSelect(c *C) { c.Assert(err, IsNil) matches(c, rows, [][]interface{}{{1, nil, nil}, {2, 2, nil}}) + // For issue 393 + mustExecSQL(c, se, "drop table if exists t") + mustExecSQL(c, se, "create table t (b blob)") + mustExecSQL(c, se, `insert t values('\x01')`) + + r = mustExecSQL(c, se, `select length(b) from t`) + row, err = r.FirstRow() + c.Assert(err, IsNil) + match(c, row, 3) + } func (s *testSessionSuite) TestSubQuery(c *C) { diff --git a/util/stringutil/string_util.go b/util/stringutil/string_util.go new file mode 100644 index 0000000000..b1d1b346bd --- /dev/null +++ b/util/stringutil/string_util.go @@ -0,0 +1,57 @@ +// Copyright 2014 The ql Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSES/QL-LICENSE file. + +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package stringutil + +import ( + "bytes" + "strings" +) + +// See: https://dev.mysql.com/doc/refman/5.7/en/string-literals.html#character-escape-sequences +const validEscapeChars = `0'"bntrz\\%_` + +// RemoveUselessBackslash removes backslashs which could be ignored in the string literal. +// See: https://dev.mysql.com/doc/refman/5.7/en/string-literals.html +// " Each of these sequences begins with a backslash (“\”), known as the escape character. +// MySQL recognizes the escape sequences shown in Table 9.1, “Special Character Escape Sequences”. +// For all other escape sequences, backslash is ignored. That is, the escaped character is +// interpreted as if it was not escaped. For example, “\x” is just “x”. These sequences are case sensitive. +// For example, “\b” is interpreted as a backspace, but “\B” is interpreted as “B”." +func RemoveUselessBackslash(s string) string { + var ( + buf bytes.Buffer + i = 0 + ) + for i < len(s)-1 { + if s[i] != '\\' { + buf.WriteByte(s[i]) + i++ + continue + } + next := s[i+1] + if strings.IndexByte(validEscapeChars, next) != -1 { + buf.WriteByte(s[i]) + } + buf.WriteByte(next) + i += 2 + } + if i == len(s)-1 { + buf.WriteByte(s[i]) + } + return buf.String() +} diff --git a/util/stringutil/string_util_test.go b/util/stringutil/string_util_test.go new file mode 100644 index 0000000000..9201c63a45 --- /dev/null +++ b/util/stringutil/string_util_test.go @@ -0,0 +1,47 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package stringutil + +import ( + "testing" + + . "github.com/pingcap/check" +) + +func TestT(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&testStringUtilSuite{}) + +type testStringUtilSuite struct { +} + +func (s *testStringUtilSuite) TestRemoveUselessBackslash(c *C) { + table := []struct { + str string + expect string + }{ + {"xxxx", "xxxx"}, + {`\x01`, `x01`}, + {`\b01`, `\b01`}, + {`\B01`, `B01`}, + {`'\'a\''`, `'\'a\''`}, + } + + for _, t := range table { + x := RemoveUselessBackslash(t.str) + c.Assert(x, Equals, t.expect) + } +}