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

     1  // Copyright 2020 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 tree
    12  
    13  import (
    14  	"encoding/csv"
    15  	"io"
    16  	"os"
    17  	"path/filepath"
    18  	"strconv"
    19  	"testing"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  	"github.com/lib/pq/oid"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  // TestOperatorVolatilityMatchesPostgres checks that our defined operators match
    28  // Postgres' operators for Volatility.
    29  //
    30  // Dump command below:
    31  // COPY (
    32  //   SELECT o.oprname, o.oprleft, o.oprright, o.oprresult, p.provolatile, p.proleakproof
    33  //   FROM pg_operator AS o JOIN pg_proc AS p ON (o.oprcode = p.oid)
    34  //   ORDER BY o.oprname, o.oprleft, o.oprright, o.oprresult
    35  // ) TO STDOUT WITH CSV DELIMITER '|' HEADER;
    36  func TestOperatorVolatilityMatchesPostgres(t *testing.T) {
    37  	defer leaktest.AfterTest(t)()
    38  	csvPath := filepath.Join("testdata", "pg_operator_provolatile_dump.csv")
    39  	f, err := os.Open(csvPath)
    40  	require.NoError(t, err)
    41  
    42  	defer f.Close()
    43  
    44  	reader := csv.NewReader(f)
    45  	reader.Comma = '|'
    46  
    47  	// Read header row
    48  	_, err = reader.Read()
    49  	require.NoError(t, err)
    50  
    51  	type pgOp struct {
    52  		name       string
    53  		leftType   oid.Oid
    54  		rightType  oid.Oid
    55  		volatility Volatility
    56  	}
    57  	var pgOps []pgOp
    58  	for {
    59  		line, err := reader.Read()
    60  		if err == io.EOF {
    61  			break
    62  		}
    63  		// Columns are:
    64  		//     0       1       2        3         4           5
    65  		//     oprname oprleft oprright oprresult provolatile proleakproof
    66  		require.NoError(t, err)
    67  		require.Len(t, line, 6)
    68  
    69  		name := line[0]
    70  
    71  		leftOid, err := strconv.Atoi(line[1])
    72  		require.NoError(t, err)
    73  
    74  		rightOid, err := strconv.Atoi(line[2])
    75  		require.NoError(t, err)
    76  
    77  		provolatile := line[4]
    78  		require.Len(t, provolatile, 1)
    79  		proleakproof := line[5]
    80  		require.Len(t, proleakproof, 1)
    81  
    82  		v, err := VolatilityFromPostgres(provolatile, proleakproof[0] == 't')
    83  		require.NoError(t, err)
    84  		pgOps = append(pgOps, pgOp{
    85  			name:       name,
    86  			leftType:   oid.Oid(leftOid),
    87  			rightType:  oid.Oid(rightOid),
    88  			volatility: v,
    89  		})
    90  	}
    91  
    92  	check := func(name string, leftType, rightType *types.T, volatility Volatility) {
    93  		t.Helper()
    94  		if volatility == 0 {
    95  			t.Errorf("operator %s(%v,%v) has no volatility set", name, leftType, rightType)
    96  			return
    97  		}
    98  
    99  		pgName := name
   100  		// Postgres doesn't have separate operators for IS (NOT) DISTINCT FROM; remap
   101  		// to equality.
   102  		switch name {
   103  		case IsDistinctFrom.String(), IsNotDistinctFrom.String():
   104  			pgName = EQ.String()
   105  		}
   106  
   107  		var leftOid oid.Oid
   108  		if leftType != nil {
   109  			leftOid = leftType.Oid()
   110  		}
   111  		rightOid := rightType.Oid()
   112  		for _, o := range pgOps {
   113  			if o.name == pgName && o.leftType == leftOid && o.rightType == rightOid {
   114  				if o.volatility != volatility {
   115  					t.Errorf(
   116  						"operator %s(%v,%v) has volatility %s, corresponding pg operator has %s",
   117  						name, leftType, rightType, volatility, o.volatility,
   118  					)
   119  				}
   120  				return
   121  			}
   122  		}
   123  		if testing.Verbose() {
   124  			t.Logf("operator %s(%v,%v) %d %d with volatility %s has no corresponding pg operator",
   125  				name, leftType, rightType, leftOid, rightOid, volatility,
   126  			)
   127  		}
   128  	}
   129  
   130  	// Check unary ops. We don't just go through the map so we process them in
   131  	// an orderly fashion.
   132  	for op := UnaryOperator(0); op < NumUnaryOperators; op++ {
   133  		for _, impl := range UnaryOps[op] {
   134  			o := impl.(*UnaryOp)
   135  			check(op.String(), nil /* leftType */, o.Typ, o.Volatility)
   136  		}
   137  	}
   138  
   139  	// Check comparison ops.
   140  	for op := ComparisonOperator(0); op < NumComparisonOperators; op++ {
   141  		for _, impl := range CmpOps[op] {
   142  			o := impl.(*CmpOp)
   143  			check(op.String(), o.LeftType, o.RightType, o.Volatility)
   144  		}
   145  	}
   146  
   147  	// Check binary ops.
   148  	for op := BinaryOperator(0); op < NumBinaryOperators; op++ {
   149  		for _, impl := range BinOps[op] {
   150  			o := impl.(*BinOp)
   151  			check(op.String(), o.LeftType, o.RightType, o.Volatility)
   152  		}
   153  	}
   154  }