Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expression: implement the Coercibility() function #14739

Merged
merged 11 commits into from
Feb 13, 2020
22 changes: 21 additions & 1 deletion expression/builtin_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,27 @@ type coercibilityFunctionClass struct {
}

func (c *coercibilityFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "COERCIBILITY")
if err := c.verifyArgs(args); err != nil {
return nil, err
}
bf := newBaseBuiltinFuncWithTp(ctx, args, types.ETInt, args[0].GetType().EvalType())
sig := &builtinCoercibilitySig{bf}
sig.setPbCode(tipb.ScalarFuncSig_Unspecified)
return sig, nil
}

type builtinCoercibilitySig struct {
baseBuiltinFunc
}

func (c *builtinCoercibilitySig) evalInt(_ chunk.Row) (res int64, isNull bool, err error) {
return int64(c.args[0].Coercibility()), false, nil
}

func (c *builtinCoercibilitySig) Clone() builtinFunc {
newSig := &builtinCoercibilitySig{}
newSig.cloneFrom(&c.baseBuiltinFunc)
return newSig
}

type collationFunctionClass struct {
Expand Down
4 changes: 2 additions & 2 deletions expression/builtin_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ func (s *testEvaluatorSuite) TestCharset(c *C) {
func (s *testEvaluatorSuite) TestCoercibility(c *C) {
fc := funcs[ast.Coercibility]
f, err := fc.getFunction(s.ctx, s.datumsToConstants(types.MakeDatums(nil)))
c.Assert(f, IsNil)
c.Assert(err, ErrorMatches, "*FUNCTION COERCIBILITY does not exist")
c.Assert(f, NotNil)
c.Assert(err, IsNil)
}

func (s *testEvaluatorSuite) TestCollation(c *C) {
Expand Down
110 changes: 110 additions & 0 deletions expression/collation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2020 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 expression

import (
"github.com/pingcap/parser/ast"
"github.com/pingcap/tidb/types"
)

type coercibility struct {
val Coercibility
init bool
}

func (c *coercibility) hasCoercibility() bool {
return c.init
}

func (c *coercibility) value() Coercibility {
return c.val
}

// SetCoercibility implements CollationExpr SetCoercibility interface.
func (c *coercibility) SetCoercibility(val Coercibility) {
c.val = val
c.init = true
}

// CollationExpr contains all interfaces about dealing with collation.
type CollationExpr interface {
// Coercibility returns the coercibility value which is used to check collations.
Coercibility() Coercibility

// SetCoercibility sets a specified coercibility for this expression.
SetCoercibility(val Coercibility)
}

// Coercibility values are used to check whether the collation of one item can be coerced to
// the collation of other. See https://dev.mysql.com/doc/refman/8.0/en/charset-collation-coercibility.html
type Coercibility int

const (
// CoercibilityExplicit is derived from an explicit COLLATE clause.
CoercibilityExplicit Coercibility = 0
// CoercibilityNone is derived from the concatenation of two strings with different collations.
CoercibilityNone Coercibility = 1
// CoercibilityImplicit is derived from a column or a stored routine parameter or local variable.
CoercibilityImplicit Coercibility = 2
// CoercibilitySysconst is derived from a “system constant” (the string returned by functions such as USER() or VERSION()).
CoercibilitySysconst Coercibility = 3
// CoercibilityCoercible is derived from a literal.
CoercibilityCoercible Coercibility = 4
// CoercibilityNumeric is derived from a numeric or temporal value.
CoercibilityNumeric Coercibility = 5
// CoercibilityIgnorable is derived from NULL or an expression that is derived from NULL.
CoercibilityIgnorable Coercibility = 6
)

var (
sysConstFuncs = map[string]struct{}{
ast.User: {},
ast.Version: {},
ast.Database: {},
ast.CurrentRole: {},
ast.CurrentUser: {},
}
)

func deriveCoercibilityForScarlarFunc(sf *ScalarFunction) Coercibility {
if _, ok := sysConstFuncs[sf.FuncName.L]; ok {
return CoercibilitySysconst
}
if !types.IsString(sf.RetType.Tp) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if Bytes, the collation is binary

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mysql> create table tt (b binary(10), vb varbinary(10));
mysql> insert into tt values (null, null);
mysql> select coercibility(b), coercibility(vb) from tt;
+-----------------+------------------+
| coercibility(b) | coercibility(vb) |
+-----------------+------------------+
|               2 |                2 |
+-----------------+------------------+

After testing, it seems ok to use this function since the Tp of Bytes(binary) in TiDB is 254(TypeString) now.

return CoercibilityNumeric
}
coer := CoercibilityIgnorable
for _, arg := range sf.GetArgs() {
if arg.Coercibility() < coer {
coer = arg.Coercibility()
}
}
return coer
}

func deriveCoercibilityForConstant(c *Constant) Coercibility {
if c.Value.IsNull() {
return CoercibilityIgnorable
} else if !types.IsString(c.RetType.Tp) {
return CoercibilityNumeric
}
return CoercibilityCoercible
}

func deriveCoercibilityForColumn(c *Column) Coercibility {
if !types.IsString(c.RetType.Tp) {
return CoercibilityNumeric
}
return CoercibilityImplicit
}
11 changes: 11 additions & 0 deletions expression/column.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ type Column struct {
// InOperand indicates whether this column is the inner operand of column equal condition converted
// from `[not] in (subq)`.
InOperand bool

coercibility
}

// Equal implements Expression interface.
Expand Down Expand Up @@ -588,3 +590,12 @@ func (col *Column) SupportReverseEval() bool {
func (col *Column) ReverseEval(sc *stmtctx.StatementContext, res types.Datum, rType types.RoundingType) (val types.Datum, err error) {
return types.ChangeReverseResultByUpperLowerBound(sc, col.RetType, res, rType)
}

// Coercibility returns the coercibility value which is used to check collations.
func (col *Column) Coercibility() Coercibility {
if col.hasCoercibility() {
return col.coercibility.value()
}
col.SetCoercibility(deriveCoercibilityForColumn(col))
return col.coercibility.value()
}
12 changes: 12 additions & 0 deletions expression/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type Constant struct {
// It's only used to reference a user variable provided in the `EXECUTE` statement or `COM_EXECUTE` binary protocol.
ParamMarker *ParamMarker
hashcode []byte

coercibility
}

// ParamMarker indicates param provided by COM_STMT_EXECUTE.
Expand Down Expand Up @@ -407,3 +409,13 @@ func (c *Constant) SupportReverseEval() bool {
func (c *Constant) ReverseEval(sc *stmtctx.StatementContext, res types.Datum, rType types.RoundingType) (val types.Datum, err error) {
return c.Value, nil
}

// Coercibility returns the coercibility value which is used to check collations.
func (c *Constant) Coercibility() Coercibility {
if c.hasCoercibility() {
return c.coercibility.value()
}

c.coercibility.SetCoercibility(deriveCoercibilityForConstant(c))
return c.coercibility.value()
}
2 changes: 2 additions & 0 deletions expression/constant_fold.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func init() {
// FoldConstant does constant folding optimization on an expression excluding deferred ones.
func FoldConstant(expr Expression) Expression {
e, _ := foldConstant(expr)
// keep the original coercibility values after folding
e.SetCoercibility(expr.Coercibility())
return e
}

Expand Down
1 change: 1 addition & 0 deletions expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Expression interface {
goJSON.Marshaler
VecExpr
ReverseExpr
CollationExpr

// Eval evaluates an expression through a row.
Eval(row chunk.Row) (types.Datum, error)
Expand Down
36 changes: 36 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5379,6 +5379,42 @@ func (s *testIntegrationSuite) TestCacheRefineArgs(c *C) {
tk.MustQuery("execute stmt using @p0").Check(testkit.Rows("0"))
}

func (s *testIntegrationSuite) TestCoercibility(c *C) {
tk := testkit.NewTestKit(c, s.store)

type testCase struct {
expr string
result int
}
testFunc := func(cases []testCase, suffix string) {
for _, tc := range cases {
tk.MustQuery(fmt.Sprintf("select coercibility(%v) %v", tc.expr, suffix)).Check(testkit.Rows(fmt.Sprintf("%v", tc.result)))
}
}
testFunc([]testCase{
// constants
{"1", 5}, {"null", 6}, {"'abc'", 4},
// sys-constants
{"version()", 3}, {"user()", 3}, {"database()", 3},
{"current_role()", 3}, {"current_user()", 3},
// scalar functions after constant folding
{"1+null", 5}, {"null+'abcde'", 5}, {"concat(null, 'abcde')", 4},
// non-deterministic functions
{"rand()", 5}, {"now()", 5}, {"sysdate()", 5},
}, "")

tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t (i int, r real, d datetime, t timestamp, c char(10), vc varchar(10), b binary(10), vb binary(10))")
tk.MustExec("insert into t values (null, null, null, null, null, null, null, null)")
testFunc([]testCase{
{"i", 5}, {"r", 5}, {"d", 5}, {"t", 5},
{"c", 2}, {"b", 2}, {"vb", 2}, {"vc", 2},
{"i+r", 5}, {"i*r", 5}, {"cos(r)+sin(i)", 5}, {"d+2", 5},
{"t*10", 5}, {"concat(c, vc)", 2}, {"replace(c, 'x', 'y')", 2},
}, "from t")
}

func (s *testIntegrationSuite) TestCacheConstEval(c *C) {
tk := testkit.NewTestKit(c, s.store)
orgEnable := plannercore.PreparedPlanCacheEnabled()
Expand Down
12 changes: 12 additions & 0 deletions expression/scalar_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ScalarFunction struct {
RetType *types.FieldType
Function builtinFunc
hashcode []byte
coercibility
}

// VecEvalInt evaluates this expression in a vectorized manner.
Expand Down Expand Up @@ -240,6 +241,8 @@ func (sf *ScalarFunction) Clone() Expression {
RetType: sf.RetType,
Function: sf.Function.Clone(),
hashcode: sf.hashcode,

coercibility: sf.coercibility,
}
}

Expand Down Expand Up @@ -419,3 +422,12 @@ func (sf *ScalarFunction) resolveIndices(schema *Schema) error {
}
return nil
}

// Coercibility returns the coercibility value which is used to check collations.
func (sf *ScalarFunction) Coercibility() Coercibility {
if sf.hasCoercibility() {
return sf.coercibility.value()
}
sf.SetCoercibility(deriveCoercibilityForScarlarFunc(sf))
return sf.coercibility.value()
}
2 changes: 2 additions & 0 deletions expression/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,5 @@ func (m *MockExpr) ExplainNormalizedInfo() string { return "
func (m *MockExpr) HashCode(sc *stmtctx.StatementContext) []byte { return nil }
func (m *MockExpr) Vectorized() bool { return false }
func (m *MockExpr) SupportReverseEval() bool { return false }
func (m *MockExpr) Coercibility() Coercibility { return 0 }
func (m *MockExpr) SetCoercibility(Coercibility) {}