// Copyright 2017 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 (
	"time"

	. "github.com/pingcap/check"
	"github.com/pingcap/parser/charset"
	"github.com/pingcap/parser/mysql"
	"github.com/pingcap/tidb/sessionctx/stmtctx"
	"github.com/pingcap/tidb/types"
	"github.com/pingcap/tidb/types/json"
	"github.com/pingcap/tidb/util/chunk"
	"github.com/pingcap/tidb/util/codec"
	"github.com/pingcap/tidb/util/collate"
	"github.com/pingcap/tipb/go-tipb"
)

var _ = Suite(&testEvalSuite{})
var _ = SerialSuites(&testEvalSerialSuite{})

type testEvalSuite struct {
	colID int64
}

type testEvalSerialSuite struct {
}

func (s *testEvalSerialSuite) TestPBToExprWithNewCollation(c *C) {
	sc := new(stmtctx.StatementContext)
	fieldTps := make([]*types.FieldType, 1)

	cases := []struct {
		name    string
		expName string
		id      int32
		pbID    int32
	}{
		{"utf8_general_ci", "utf8_general_ci", 33, 33},
		{"UTF8MB4_BIN", "utf8mb4_bin", 46, 46},
		{"utf8mb4_bin", "utf8mb4_bin", 46, 46},
		{"utf8mb4_general_ci", "utf8mb4_general_ci", 45, 45},
		{"", "utf8mb4_bin", 46, 46},
		{"some_error_collation", "utf8mb4_bin", 46, 46},
		{"utf8_unicode_ci", "utf8_unicode_ci", 192, 192},
		{"utf8mb4_unicode_ci", "utf8mb4_unicode_ci", 224, 224},
	}

	for _, cs := range cases {
		ft := types.NewFieldType(mysql.TypeString)
		ft.Collate = cs.name
		expr := new(tipb.Expr)
		expr.Tp = tipb.ExprType_String
		expr.FieldType = toPBFieldType(ft)
		c.Assert(expr.FieldType.Collate, Equals, cs.pbID)

		e, err := PBToExpr(expr, fieldTps, sc)
		c.Assert(err, IsNil)
		cons, ok := e.(*Constant)
		c.Assert(ok, IsTrue)
		c.Assert(cons.Value.Collation(), Equals, cs.expName)
	}
	collate.SetNewCollationEnabledForTest(true)
	defer collate.SetNewCollationEnabledForTest(false)

	for _, cs := range cases {
		ft := types.NewFieldType(mysql.TypeString)
		ft.Collate = cs.name
		expr := new(tipb.Expr)
		expr.Tp = tipb.ExprType_String
		expr.FieldType = toPBFieldType(ft)
		c.Assert(expr.FieldType.Collate, Equals, -cs.pbID)

		e, err := PBToExpr(expr, fieldTps, sc)
		c.Assert(err, IsNil)
		cons, ok := e.(*Constant)
		c.Assert(ok, IsTrue)
		c.Assert(cons.Value.Collation(), Equals, cs.expName)
	}
}

func (s *testEvalSuite) SetUpSuite(c *C) {
	s.colID = 0
}

func (s *testEvalSuite) allocColID() int64 {
	s.colID++
	return s.colID
}

func (s *testEvalSuite) TearDownTest(c *C) {
	s.colID = 0
}

func (s *testEvalSuite) TestPBToExpr(c *C) {
	sc := new(stmtctx.StatementContext)
	fieldTps := make([]*types.FieldType, 1)
	ds := []types.Datum{types.NewIntDatum(1), types.NewUintDatum(1), types.NewFloat64Datum(1),
		types.NewDecimalDatum(newMyDecimal(c, "1")), types.NewDurationDatum(newDuration(time.Second))}

	for _, d := range ds {
		expr := datumExpr(c, d)
		expr.Val = expr.Val[:len(expr.Val)/2]
		_, err := PBToExpr(expr, fieldTps, sc)
		c.Assert(err, NotNil)
	}

	expr := &tipb.Expr{
		Tp: tipb.ExprType_ScalarFunc,
		Children: []*tipb.Expr{
			{
				Tp: tipb.ExprType_ValueList,
			},
		},
	}
	_, err := PBToExpr(expr, fieldTps, sc)
	c.Assert(err, IsNil)

	val := make([]byte, 0, 32)
	val = codec.EncodeInt(val, 1)
	expr = &tipb.Expr{
		Tp: tipb.ExprType_ScalarFunc,
		Children: []*tipb.Expr{
			{
				Tp:  tipb.ExprType_ValueList,
				Val: val[:len(val)/2],
			},
		},
	}
	_, err = PBToExpr(expr, fieldTps, sc)
	c.Assert(err, NotNil)

	expr = &tipb.Expr{
		Tp: tipb.ExprType_ScalarFunc,
		Children: []*tipb.Expr{
			{
				Tp:  tipb.ExprType_ValueList,
				Val: val,
			},
		},
		Sig:       tipb.ScalarFuncSig_AbsInt,
		FieldType: ToPBFieldType(newIntFieldType()),
	}
	_, err = PBToExpr(expr, fieldTps, sc)
	c.Assert(err, NotNil)
}

// TestEval test expr.Eval().
func (s *testEvalSuite) TestEval(c *C) {
	row := chunk.MutRowFromDatums([]types.Datum{types.NewDatum(100)}).ToRow()
	fieldTps := make([]*types.FieldType, 1)
	fieldTps[0] = types.NewFieldType(mysql.TypeLonglong)
	tests := []struct {
		expr   *tipb.Expr
		result types.Datum
	}{
		// Datums.
		{
			datumExpr(c, types.NewFloat32Datum(1.1)),
			types.NewFloat32Datum(1.1),
		},
		{
			datumExpr(c, types.NewFloat64Datum(1.1)),
			types.NewFloat64Datum(1.1),
		},
		{
			datumExpr(c, types.NewIntDatum(1)),
			types.NewIntDatum(1),
		},
		{
			datumExpr(c, types.NewUintDatum(1)),
			types.NewUintDatum(1),
		},
		{
			datumExpr(c, types.NewBytesDatum([]byte("abc"))),
			types.NewBytesDatum([]byte("abc")),
		},
		{
			datumExpr(c, types.NewStringDatum("abc")),
			types.NewStringDatum("abc"),
		},
		{
			datumExpr(c, types.Datum{}),
			types.Datum{},
		},
		{
			datumExpr(c, types.NewDurationDatum(types.Duration{Duration: time.Hour})),
			types.NewDurationDatum(types.Duration{Duration: time.Hour}),
		},
		{
			datumExpr(c, types.NewDecimalDatum(types.NewDecFromFloatForTest(1.1))),
			types.NewDecimalDatum(types.NewDecFromFloatForTest(1.1)),
		},
		// Columns.
		{
			columnExpr(0),
			types.NewIntDatum(100),
		},
		// Scalar Functions.
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_JsonDepthSig,
				toPBFieldType(newIntFieldType()),
				jsonDatumExpr(c, `true`),
			),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_JsonDepthSig,
				toPBFieldType(newIntFieldType()),
				jsonDatumExpr(c, `[10, {"a": 20}]`),
			),
			types.NewIntDatum(3),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_JsonStorageSizeSig,
				toPBFieldType(newIntFieldType()),
				jsonDatumExpr(c, `[{"a":{"a":1},"b":2}]`),
			),
			types.NewIntDatum(25),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_JsonSearchSig,
				toPBFieldType(newJSONFieldType()),
				jsonDatumExpr(c, `["abc", [{"k": "10"}, "def"], {"x":"abc"}, {"y":"bcd"}]`),
				datumExpr(c, types.NewBytesDatum([]byte(`all`))),
				datumExpr(c, types.NewBytesDatum([]byte(`10`))),
				datumExpr(c, types.NewBytesDatum([]byte(`\`))),
				datumExpr(c, types.NewBytesDatum([]byte(`$**.k`))),
			),
			newJSONDatum(c, `"$[1][0].k"`),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(2333))),
			types.NewIntDatum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(2333))),
			types.NewIntDatum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewStringDatum("2333"))),
			types.NewIntDatum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2333")))),
			types.NewIntDatum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewIntDatum(2333))),
			types.NewFloat64Datum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(2333))),
			types.NewFloat64Datum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewStringDatum("2333"))),
			types.NewFloat64Datum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2333")))),
			types.NewFloat64Datum(2333),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewStringDatum("2333"))),
			types.NewStringDatum("2333"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewIntDatum(2333))),
			types.NewStringDatum("2333"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewFloat64Datum(2333))),
			types.NewStringDatum("2333"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2333")))),
			types.NewStringDatum("2333"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2333")))),
			types.NewDecimalDatum(newMyDecimal(c, "2333")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewIntDatum(2333))),
			types.NewDecimalDatum(newMyDecimal(c, "2333")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewFloat64Datum(2333))),
			types.NewDecimalDatum(newMyDecimal(c, "2333")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewStringDatum("2333"))),
			types.NewDecimalDatum(newMyDecimal(c, "2333")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(2)), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(2)), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LTReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_EQReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LTDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_EQDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*2))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GTDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*2))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_EQDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*2)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*2)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEString,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewStringDatum("1")), datumExpr(c, types.NewStringDatum("1"))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEString,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewStringDatum("1")), datumExpr(c, types.NewStringDatum("1"))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEString,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewStringDatum("2")), datumExpr(c, types.NewStringDatum("1"))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQString,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GTJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[2]"), jsonDatumExpr(c, "[1]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_GEJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[2]"), jsonDatumExpr(c, "[1]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LTJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[1]"), jsonDatumExpr(c, "[2]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LEJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[1]"), jsonDatumExpr(c, "[2]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_EQJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[1]"), jsonDatumExpr(c, "[1]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NEJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[1]"), jsonDatumExpr(c, "[2]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_NullEQJson,
				toPBFieldType(newIntFieldType()), jsonDatumExpr(c, "[1]"), jsonDatumExpr(c, "[1]")),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_DecimalIsNull,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_DurationIsNull,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_RealIsNull,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LeftShift,
				ToPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(1)), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(2),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_AbsInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(-1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_AbsUInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewUintDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_AbsReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(-1.23))),
			types.NewFloat64Datum(1.23),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_AbsDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "-1.23")))),
			types.NewDecimalDatum(newMyDecimal(c, "1.23")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LogicalAnd,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LogicalOr,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(0))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_LogicalXor,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(0))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_BitAndSig,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_BitOrSig,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(0))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_BitXorSig,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(0))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_BitNegSig,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(0))),
			types.NewIntDatum(-1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_InReal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_InDecimal,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_InString,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewStringDatum("1")), datumExpr(c, types.NewStringDatum("1"))),
			types.NewIntDatum(1),
		},
		//{
		//	scalarFunctionExpr(tipb.ScalarFuncSig_InTime,
		//		toPBFieldType(newIntFieldType()), datumExpr(c, types.NewTimeDatum(types.ZeroDate)), datumExpr(c, types.NewTimeDatum(types.ZeroDate))),
		//	types.NewIntDatum(1),
		//},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_InDuration,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second))), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfNullInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(2),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfNullReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewFloat64Datum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewFloat64Datum(2),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfNullDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewDecimalDatum(newMyDecimal(c, "1")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewDecimalDatum(newMyDecimal(c, "2")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfNullString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewStringDatum("1"))),
			types.NewStringDatum("1"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewStringDatum("2"))),
			types.NewStringDatum("2"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfNullDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewDatum(nil)), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewDurationDatum(newDuration(time.Second)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_IfDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*2)))),
			types.NewDurationDatum(newDuration(time.Second * 2)),
		},

		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewDurationDatum(newDuration(time.Second * 1)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewDurationDatum(newDuration(time.Second * 1)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewDurationDatum(newDuration(time.Second * 1)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDurationAsDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second*1)))),
			types.NewDurationDatum(newDuration(time.Second * 1)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewStringDatum("1"))),
			types.NewDurationDatum(newDuration(time.Second * 1)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastTimeAsTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewTimeDatum(newDateTime(c, "2000-01-01")))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastIntAsTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewIntDatum(20000101))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastRealAsTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewFloat64Datum(20000101))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastDecimalAsTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "20000101")))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CastStringAsTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewStringDatum("20000101"))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_PlusInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(3),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_PlusDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewDecimalDatum(newMyDecimal(c, "3")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_PlusReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewFloat64Datum(3),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MinusInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(-1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MinusDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewDecimalDatum(newMyDecimal(c, "-1")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MinusReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewFloat64Datum(-1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MultiplyInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1)), datumExpr(c, types.NewIntDatum(2))),
			types.NewIntDatum(2),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MultiplyDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1"))), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "2")))),
			types.NewDecimalDatum(newMyDecimal(c, "2")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_MultiplyReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1)), datumExpr(c, types.NewFloat64Datum(2))),
			types.NewFloat64Datum(2),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CeilIntToInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CeilIntToDec,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewDecimalDatum(newMyDecimal(c, "1")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CeilDecToInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CeilReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewFloat64Datum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_FloorIntToInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_FloorIntToDec,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewDecimalDatum(newMyDecimal(c, "1")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_FloorDecToInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_FloorReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewFloat64Datum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceInt,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewIntDatum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceReal,
				toPBFieldType(newRealFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewFloat64Datum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceDecimal,
				toPBFieldType(newDecimalFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewDecimalDatum(newMyDecimal(c, "1")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceString,
				toPBFieldType(newStringFieldType()), datumExpr(c, types.NewStringDatum("1"))),
			types.NewStringDatum("1"),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceDuration,
				toPBFieldType(newDurFieldType()), datumExpr(c, types.NewDurationDatum(newDuration(time.Second)))),
			types.NewDurationDatum(newDuration(time.Second)),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CoalesceTime,
				toPBFieldType(newDateFieldType()), datumExpr(c, types.NewTimeDatum(newDateTime(c, "2000-01-01")))),
			types.NewTimeDatum(newDateTime(c, "2000-01-01")),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenInt,
				toPBFieldType(newIntFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenReal,
				toPBFieldType(newRealFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenDecimal,
				toPBFieldType(newDecimalFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenDuration,
				toPBFieldType(newDurFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenTime,
				toPBFieldType(newDateFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_CaseWhenJson,
				toPBFieldType(newJSONFieldType())),
			types.NewDatum(nil),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_RealIsFalse,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewIntDatum(0),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_DecimalIsFalse,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(0),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_RealIsTrue,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewFloat64Datum(1))),
			types.NewIntDatum(1),
		},
		{
			scalarFunctionExpr(tipb.ScalarFuncSig_DecimalIsTrue,
				toPBFieldType(newIntFieldType()), datumExpr(c, types.NewDecimalDatum(newMyDecimal(c, "1")))),
			types.NewIntDatum(1),
		},
	}
	sc := new(stmtctx.StatementContext)
	for _, tt := range tests {
		expr, err := PBToExpr(tt.expr, fieldTps, sc)
		c.Assert(err, IsNil)
		result, err := expr.Eval(row)
		c.Assert(err, IsNil)
		c.Assert(result.Kind(), Equals, tt.result.Kind())
		cmp, err := result.CompareDatum(sc, &tt.result)
		c.Assert(err, IsNil)
		c.Assert(cmp, Equals, 0)
	}
}

func datumExpr(c *C, d types.Datum) *tipb.Expr {
	expr := new(tipb.Expr)
	switch d.Kind() {
	case types.KindInt64:
		expr.Tp = tipb.ExprType_Int64
		expr.Val = codec.EncodeInt(nil, d.GetInt64())
	case types.KindUint64:
		expr.Tp = tipb.ExprType_Uint64
		expr.Val = codec.EncodeUint(nil, d.GetUint64())
	case types.KindString:
		expr.Tp = tipb.ExprType_String
		expr.FieldType = toPBFieldType(types.NewFieldType(mysql.TypeString))
		expr.Val = d.GetBytes()
	case types.KindBytes:
		expr.Tp = tipb.ExprType_Bytes
		expr.Val = d.GetBytes()
	case types.KindFloat32:
		expr.Tp = tipb.ExprType_Float32
		expr.Val = codec.EncodeFloat(nil, d.GetFloat64())
	case types.KindFloat64:
		expr.Tp = tipb.ExprType_Float64
		expr.Val = codec.EncodeFloat(nil, d.GetFloat64())
	case types.KindMysqlDuration:
		expr.Tp = tipb.ExprType_MysqlDuration
		expr.Val = codec.EncodeInt(nil, int64(d.GetMysqlDuration().Duration))
	case types.KindMysqlDecimal:
		expr.Tp = tipb.ExprType_MysqlDecimal
		var err error
		expr.Val, err = codec.EncodeDecimal(nil, d.GetMysqlDecimal(), d.Length(), d.Frac())
		c.Assert(err, IsNil)
	case types.KindMysqlJSON:
		expr.Tp = tipb.ExprType_MysqlJson
		var err error
		expr.Val = make([]byte, 0, 1024)
		expr.Val, err = codec.EncodeValue(nil, expr.Val, d)
		c.Assert(err, IsNil)
	case types.KindMysqlTime:
		expr.Tp = tipb.ExprType_MysqlTime
		var err error
		expr.Val, err = codec.EncodeMySQLTime(nil, d.GetMysqlTime(), mysql.TypeUnspecified, nil)
		c.Assert(err, IsNil)
		expr.FieldType = ToPBFieldType(newDateFieldType())
	default:
		expr.Tp = tipb.ExprType_Null
	}
	return expr
}

func newJSONDatum(c *C, s string) (d types.Datum) {
	j, err := json.ParseBinaryFromString(s)
	c.Assert(err, IsNil)
	d.SetMysqlJSON(j)
	return d
}

func jsonDatumExpr(c *C, s string) *tipb.Expr {
	return datumExpr(c, newJSONDatum(c, s))
}

func columnExpr(columnID int64) *tipb.Expr {
	expr := new(tipb.Expr)
	expr.Tp = tipb.ExprType_ColumnRef
	expr.Val = codec.EncodeInt(nil, columnID)
	return expr
}

// toPBFieldType converts *types.FieldType to *tipb.FieldType.
func toPBFieldType(ft *types.FieldType) *tipb.FieldType {
	return &tipb.FieldType{
		Tp:      int32(ft.Tp),
		Flag:    uint32(ft.Flag),
		Flen:    int32(ft.Flen),
		Decimal: int32(ft.Decimal),
		Charset: ft.Charset,
		Collate: collationToProto(ft.Collate),
	}
}

func newMyDecimal(c *C, s string) *types.MyDecimal {
	d := new(types.MyDecimal)
	c.Assert(d.FromString([]byte(s)), IsNil)
	return d
}

func newDuration(dur time.Duration) types.Duration {
	return types.Duration{
		Duration: dur,
		Fsp:      types.DefaultFsp,
	}
}

func newDateTime(c *C, s string) types.Time {
	t, err := types.ParseDate(nil, s)
	c.Assert(err, IsNil)
	return t
}

func newDateFieldType() *types.FieldType {
	return &types.FieldType{
		Tp: mysql.TypeDate,
	}
}

func newIntFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeLonglong,
		Flen:    mysql.MaxIntWidth,
		Decimal: 0,
		Flag:    mysql.BinaryFlag,
	}
}

func newDurFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeDuration,
		Decimal: int(types.DefaultFsp),
	}
}

func newStringFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:   mysql.TypeVarString,
		Flen: types.UnspecifiedLength,
	}
}

func newRealFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:   mysql.TypeFloat,
		Flen: types.UnspecifiedLength,
	}
}

func newDecimalFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:   mysql.TypeNewDecimal,
		Flen: types.UnspecifiedLength,
	}
}

func newJSONFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeJSON,
		Flen:    types.UnspecifiedLength,
		Decimal: 0,
		Charset: charset.CharsetBin,
		Collate: charset.CollationBin,
	}
}

func newFloatFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeFloat,
		Flen:    types.UnspecifiedLength,
		Decimal: 0,
		Charset: charset.CharsetBin,
		Collate: charset.CollationBin,
	}
}

func newBinaryLiteralFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeBit,
		Flen:    types.UnspecifiedLength,
		Decimal: 0,
		Charset: charset.CharsetBin,
		Collate: charset.CollationBin,
	}
}

func newBlobFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeBlob,
		Flen:    types.UnspecifiedLength,
		Decimal: 0,
		Charset: charset.CharsetBin,
		Collate: charset.CollationBin,
	}
}

func newEnumFieldType() *types.FieldType {
	return &types.FieldType{
		Tp:      mysql.TypeEnum,
		Flen:    types.UnspecifiedLength,
		Decimal: 0,
		Charset: charset.CharsetBin,
		Collate: charset.CollationBin,
	}
}

func scalarFunctionExpr(sigCode tipb.ScalarFuncSig, retType *tipb.FieldType, args ...*tipb.Expr) *tipb.Expr {
	return &tipb.Expr{
		Tp:        tipb.ExprType_ScalarFunc,
		Sig:       sigCode,
		Children:  args,
		FieldType: retType,
	}
}
