github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colexec/cast_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package colexec
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/col/coldata"
    19  	"github.com/cockroachdb/cockroach/pkg/col/coldatatestutils"
    20  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func TestRandomizedCast(t *testing.T) {
    32  	defer leaktest.AfterTest(t)()
    33  	ctx := context.Background()
    34  	st := cluster.MakeTestingClusterSettings()
    35  	evalCtx := tree.MakeTestingEvalContext(st)
    36  	defer evalCtx.Stop(ctx)
    37  	flowCtx := &execinfra.FlowCtx{
    38  		EvalCtx: &evalCtx,
    39  		Cfg: &execinfra.ServerConfig{
    40  			Settings: st,
    41  		},
    42  	}
    43  
    44  	datumAsBool := func(d tree.Datum) interface{} {
    45  		return bool(tree.MustBeDBool(d))
    46  	}
    47  	datumAsInt := func(d tree.Datum) interface{} {
    48  		return int(tree.MustBeDInt(d))
    49  	}
    50  	datumAsFloat := func(d tree.Datum) interface{} {
    51  		return float64(tree.MustBeDFloat(d))
    52  	}
    53  	datumAsDecimal := func(d tree.Datum) interface{} {
    54  		return tree.MustBeDDecimal(d).Decimal
    55  	}
    56  
    57  	tc := []struct {
    58  		fromTyp      *types.T
    59  		fromPhysType func(tree.Datum) interface{}
    60  		toTyp        *types.T
    61  		toPhysType   func(tree.Datum) interface{}
    62  		// Some types casting can fail, so retry if we
    63  		// generate a datum that is unable to be casted.
    64  		retryGeneration bool
    65  	}{
    66  		//bool -> t tests
    67  		{types.Bool, datumAsBool, types.Bool, datumAsBool, false},
    68  		{types.Bool, datumAsBool, types.Int, datumAsInt, false},
    69  		{types.Bool, datumAsBool, types.Float, datumAsFloat, false},
    70  		// decimal -> t tests
    71  		{types.Decimal, datumAsDecimal, types.Bool, datumAsBool, false},
    72  		// int -> t tests
    73  		{types.Int, datumAsInt, types.Bool, datumAsBool, false},
    74  		{types.Int, datumAsInt, types.Float, datumAsFloat, false},
    75  		{types.Int, datumAsInt, types.Decimal, datumAsDecimal, false},
    76  		// float -> t tests
    77  		{types.Float, datumAsFloat, types.Bool, datumAsBool, false},
    78  		// We can sometimes generate a float outside of the range of the integers,
    79  		// so we want to retry with generation if that occurs.
    80  		{types.Float, datumAsFloat, types.Int, datumAsInt, true},
    81  		{types.Float, datumAsFloat, types.Decimal, datumAsDecimal, false},
    82  	}
    83  
    84  	rng, _ := randutil.NewPseudoRand()
    85  
    86  	for _, c := range tc {
    87  		t.Run(fmt.Sprintf("%sTo%s", c.fromTyp.String(), c.toTyp.String()), func(t *testing.T) {
    88  			n := 100
    89  			// Make an input vector of length n.
    90  			input := tuples{}
    91  			output := tuples{}
    92  			for i := 0; i < n; i++ {
    93  				// We don't allow any NULL datums to be generated, so disable
    94  				// this ability in the RandDatum function.
    95  				fromDatum := sqlbase.RandDatum(rng, c.fromTyp, false)
    96  				var (
    97  					toDatum tree.Datum
    98  					err     error
    99  				)
   100  				toDatum, err = tree.PerformCast(&evalCtx, fromDatum, c.toTyp)
   101  				if c.retryGeneration {
   102  					for err != nil {
   103  						// If we are allowed to retry, make a new datum and cast it on error.
   104  						fromDatum = sqlbase.RandDatum(rng, c.fromTyp, false)
   105  						toDatum, err = tree.PerformCast(&evalCtx, fromDatum, c.toTyp)
   106  					}
   107  				} else {
   108  					if err != nil {
   109  						t.Fatal(err)
   110  					}
   111  				}
   112  				input = append(input, tuple{c.fromPhysType(fromDatum)})
   113  				output = append(output, tuple{c.fromPhysType(fromDatum), c.toPhysType(toDatum)})
   114  			}
   115  			runTests(t, []tuples{input}, output, orderedVerifier,
   116  				func(input []colexecbase.Operator) (colexecbase.Operator, error) {
   117  					return createTestCastOperator(ctx, flowCtx, input[0], c.fromTyp, c.toTyp)
   118  				})
   119  		})
   120  	}
   121  }
   122  
   123  func BenchmarkCastOp(b *testing.B) {
   124  	ctx := context.Background()
   125  	st := cluster.MakeTestingClusterSettings()
   126  	evalCtx := tree.MakeTestingEvalContext(st)
   127  	defer evalCtx.Stop(ctx)
   128  	flowCtx := &execinfra.FlowCtx{
   129  		EvalCtx: &evalCtx,
   130  		Cfg: &execinfra.ServerConfig{
   131  			Settings: st,
   132  		},
   133  	}
   134  	rng, _ := randutil.NewPseudoRand()
   135  	for _, typePair := range [][]*types.T{
   136  		{types.Int, types.Float},
   137  		{types.Int, types.Decimal},
   138  		{types.Float, types.Decimal},
   139  	} {
   140  		for _, useSel := range []bool{true, false} {
   141  			for _, hasNulls := range []bool{true, false} {
   142  				b.Run(
   143  					fmt.Sprintf("useSel=%t/hasNulls=%t/%s_to_%s",
   144  						useSel, hasNulls, typePair[0].Name(), typePair[1].Name(),
   145  					), func(b *testing.B) {
   146  						nullProbability := nullProbability
   147  						if !hasNulls {
   148  							nullProbability = 0
   149  						}
   150  						selectivity := selectivity
   151  						if !useSel {
   152  							selectivity = 1.0
   153  						}
   154  						typs := []*types.T{typePair[0]}
   155  						batch := coldatatestutils.RandomBatchWithSel(
   156  							testAllocator, rng, typs,
   157  							coldata.BatchSize(), nullProbability, selectivity,
   158  						)
   159  						source := colexecbase.NewRepeatableBatchSource(testAllocator, batch, typs)
   160  						op, err := createTestCastOperator(ctx, flowCtx, source, typePair[0], typePair[1])
   161  						require.NoError(b, err)
   162  						b.SetBytes(int64(8 * coldata.BatchSize()))
   163  						b.ResetTimer()
   164  						op.Init()
   165  						for i := 0; i < b.N; i++ {
   166  							op.Next(ctx)
   167  						}
   168  					})
   169  			}
   170  		}
   171  	}
   172  }
   173  
   174  func createTestCastOperator(
   175  	ctx context.Context,
   176  	flowCtx *execinfra.FlowCtx,
   177  	input colexecbase.Operator,
   178  	fromTyp *types.T,
   179  	toTyp *types.T,
   180  ) (colexecbase.Operator, error) {
   181  	// We currently don't support casting to decimal type (other than when
   182  	// casting from decimal with the same precision), so we will allow falling
   183  	// back to row-by-row engine.
   184  	return createTestProjectingOperator(
   185  		ctx, flowCtx, input, []*types.T{fromTyp},
   186  		fmt.Sprintf("@1::%s", toTyp.Name()), true, /* canFallbackToRowexec */
   187  	)
   188  }