github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/invertedexpr/expression_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 invertedexpr
    12  
    13  import (
    14  	"fmt"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    19  	"github.com/cockroachdb/cockroach/pkg/util/treeprinter"
    20  	"github.com/cockroachdb/datadriven"
    21  	"github.com/gogo/protobuf/proto"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  /*
    26  Format for the datadriven test:
    27  
    28  new-span-leaf name=<name> tight=<true|false> span=<start>[,<end>]
    29  ----
    30  <SpanExpression as string>
    31  
    32    Creates a new leaf SpanExpression with the given name
    33  
    34  new-unknown-leaf name=<name> tight=<true|false>
    35  ----
    36  
    37    Creates a new leaf unknownExpression with the given name
    38  
    39  new-non-inverted-leaf name=<name>
    40  ----
    41  
    42    Creates a new NonInvertedColExpression with the given name
    43  
    44  and result=<name> left=<name> right=<name>
    45  ----
    46  <SpanExpression as string>
    47  
    48    Ands the left and right expressions and stores the result
    49  
    50  or result=<name> left=<name> right=<name>
    51  ----
    52  <SpanExpression as string>
    53  
    54    Ors the left and right expressions and stores the result
    55  
    56  to-proto name=<name>
    57  ----
    58  <SpanExpressionProto as string>
    59  
    60    Converts the SpanExpression to SpanExpressionProto
    61  */
    62  
    63  func getSpan(t *testing.T, d *datadriven.TestData) InvertedSpan {
    64  	var str string
    65  	d.ScanArgs(t, "span", &str)
    66  	parts := strings.Split(str, ",")
    67  	if len(parts) > 2 {
    68  		d.Fatalf(t, "incorrect span format: %s", str)
    69  		return InvertedSpan{}
    70  	} else if len(parts) == 2 {
    71  		return InvertedSpan{start: []byte(parts[0]), end: []byte(parts[1])}
    72  	} else {
    73  		return MakeSingleInvertedValSpan([]byte(parts[0]))
    74  	}
    75  }
    76  
    77  type UnknownExpression struct {
    78  	tight bool
    79  }
    80  
    81  func (u UnknownExpression) IsTight() bool { return u.tight }
    82  func (u UnknownExpression) SetNotTight()  { u.tight = false }
    83  func (u UnknownExpression) String() string {
    84  	return fmt.Sprintf("UnknownExpression: tight=%t", u.tight)
    85  }
    86  
    87  // Makes a (shallow) copy of the root node of the expression identified
    88  // by name, since calls to And() and Or() can modify that root node, and
    89  // the test wants to preserve the unmodified expression for later use.
    90  func getExprCopy(
    91  	t *testing.T, d *datadriven.TestData, name string, exprsByName map[string]InvertedExpression,
    92  ) InvertedExpression {
    93  	expr := exprsByName[name]
    94  	if expr == nil {
    95  		d.Fatalf(t, "unknown expr: %s", name)
    96  	}
    97  	switch e := expr.(type) {
    98  	case *SpanExpression:
    99  		return &SpanExpression{
   100  			Tight:              e.Tight,
   101  			SpansToRead:        append([]InvertedSpan(nil), e.SpansToRead...),
   102  			FactoredUnionSpans: append([]InvertedSpan(nil), e.FactoredUnionSpans...),
   103  			Operator:           e.Operator,
   104  			Left:               e.Left,
   105  			Right:              e.Right,
   106  		}
   107  	case NonInvertedColExpression:
   108  		return NonInvertedColExpression{}
   109  	case UnknownExpression:
   110  		return UnknownExpression{tight: e.tight}
   111  	default:
   112  		d.Fatalf(t, "unknown expr type")
   113  		return nil
   114  	}
   115  }
   116  
   117  func toString(expr InvertedExpression) string {
   118  	tp := treeprinter.New()
   119  	formatExpression(tp, expr)
   120  	return tp.String()
   121  }
   122  
   123  func getLeftAndRightExpr(
   124  	t *testing.T, d *datadriven.TestData, exprsByName map[string]InvertedExpression,
   125  ) (InvertedExpression, InvertedExpression) {
   126  	var leftName, rightName string
   127  	d.ScanArgs(t, "left", &leftName)
   128  	d.ScanArgs(t, "right", &rightName)
   129  	return getExprCopy(t, d, leftName, exprsByName), getExprCopy(t, d, rightName, exprsByName)
   130  }
   131  
   132  func TestExpression(t *testing.T) {
   133  	defer leaktest.AfterTest(t)()
   134  	exprsByName := make(map[string]InvertedExpression)
   135  
   136  	datadriven.RunTest(t, "testdata/expression", func(t *testing.T, d *datadriven.TestData) string {
   137  		switch d.Cmd {
   138  		case "new-span-leaf":
   139  			var name string
   140  			d.ScanArgs(t, "name", &name)
   141  			var tight bool
   142  			d.ScanArgs(t, "tight", &tight)
   143  			span := getSpan(t, d)
   144  			expr := ExprForInvertedSpan(span, tight)
   145  			exprsByName[name] = expr
   146  			return expr.String()
   147  		case "new-unknown-leaf":
   148  			var name string
   149  			d.ScanArgs(t, "name", &name)
   150  			var tight bool
   151  			d.ScanArgs(t, "tight", &tight)
   152  			expr := UnknownExpression{tight: tight}
   153  			exprsByName[name] = expr
   154  			return fmt.Sprintf("%v", expr)
   155  		case "new-non-inverted-leaf":
   156  			var name string
   157  			d.ScanArgs(t, "name", &name)
   158  			exprsByName[name] = NonInvertedColExpression{}
   159  			return ""
   160  		case "and":
   161  			var name string
   162  			d.ScanArgs(t, "result", &name)
   163  			left, right := getLeftAndRightExpr(t, d, exprsByName)
   164  			expr := And(left, right)
   165  			exprsByName[name] = expr
   166  			return toString(expr)
   167  		case "or":
   168  			var name string
   169  			d.ScanArgs(t, "result", &name)
   170  			left, right := getLeftAndRightExpr(t, d, exprsByName)
   171  			expr := Or(left, right)
   172  			exprsByName[name] = expr
   173  			return toString(expr)
   174  		case "to-proto":
   175  			var name string
   176  			d.ScanArgs(t, "name", &name)
   177  			expr := exprsByName[name]
   178  			if expr == nil {
   179  				expr = (*SpanExpression)(nil)
   180  			}
   181  			return proto.MarshalTextString(expr.(*SpanExpression).ToProto())
   182  		default:
   183  			return fmt.Sprintf("unknown command: %s", d.Cmd)
   184  		}
   185  	})
   186  }
   187  
   188  func span(start, end string) InvertedSpan {
   189  	return InvertedSpan{start: []byte(start), end: []byte(end)}
   190  }
   191  
   192  func single(start string) InvertedSpan {
   193  	return MakeSingleInvertedValSpan([]byte(start))
   194  }
   195  
   196  func checkEqual(t *testing.T, expected, actual []InvertedSpan) {
   197  	require.Equal(t, len(expected), len(actual))
   198  	for i := range expected {
   199  		require.Equal(t, expected[i].start, actual[i].start)
   200  		require.Equal(t, expected[i].end, actual[i].end)
   201  	}
   202  }
   203  
   204  func TestSetUnion(t *testing.T) {
   205  	checkEqual(t,
   206  		[]InvertedSpan{span("b", "b\x00")},
   207  		unionSpans(
   208  			[]InvertedSpan{single("b")},
   209  			[]InvertedSpan{span("b", "b\x00")},
   210  		),
   211  	)
   212  	checkEqual(t,
   213  		[]InvertedSpan{span("b", "b\x00\x00")},
   214  		unionSpans(
   215  			[]InvertedSpan{single("b")},
   216  			[]InvertedSpan{single("b\x00")},
   217  		),
   218  	)
   219  	checkEqual(t,
   220  		[]InvertedSpan{span("b", "c")},
   221  		unionSpans(
   222  			[]InvertedSpan{single("b")},
   223  			[]InvertedSpan{span("b\x00", "c")},
   224  		),
   225  	)
   226  	checkEqual(t,
   227  		[]InvertedSpan{span("b", "c")},
   228  		unionSpans(
   229  			[]InvertedSpan{span("b", "b\x00")},
   230  			[]InvertedSpan{span("b", "c")},
   231  		),
   232  	)
   233  	checkEqual(t,
   234  		[]InvertedSpan{span("b", "c"), single("d\x00")},
   235  		unionSpans(
   236  			[]InvertedSpan{span("b", "c")},
   237  			[]InvertedSpan{single("d\x00")},
   238  		),
   239  	)
   240  	checkEqual(t,
   241  		[]InvertedSpan{span("b", "c"), single("d"), single("e")},
   242  		unionSpans(
   243  			[]InvertedSpan{span("b", "c"), single("e")},
   244  			[]InvertedSpan{single("d")},
   245  		),
   246  	)
   247  	checkEqual(t,
   248  		[]InvertedSpan{span("b", "d"), single("e")},
   249  		unionSpans(
   250  			[]InvertedSpan{span("b", "c"), single("e")},
   251  			[]InvertedSpan{span("c", "d")},
   252  		),
   253  	)
   254  	checkEqual(t,
   255  		[]InvertedSpan{span("b", "f")},
   256  		unionSpans(
   257  			[]InvertedSpan{span("b", "c"), single("e")},
   258  			[]InvertedSpan{span("c", "f")},
   259  		),
   260  	)
   261  }
   262  
   263  func TestSetIntersection(t *testing.T) {
   264  	checkEqual(t,
   265  		[]InvertedSpan{single("b")},
   266  		intersectSpans(
   267  			[]InvertedSpan{single("b")},
   268  			[]InvertedSpan{span("b", "b\x00")},
   269  		),
   270  	)
   271  	checkEqual(t,
   272  		nil,
   273  		intersectSpans(
   274  			[]InvertedSpan{single("b")},
   275  			[]InvertedSpan{span("b\x00", "c")},
   276  		),
   277  	)
   278  	checkEqual(t,
   279  		[]InvertedSpan{single("b"), span("d", "d\x00"), span("dd", "e"), span("f", "ff")},
   280  		intersectSpans(
   281  			[]InvertedSpan{single("b"), span("d", "e"), span("f", "g")},
   282  			[]InvertedSpan{span("b", "d\x00"), span("dd", "ff")},
   283  		),
   284  	)
   285  }
   286  
   287  func TestSetSubtraction(t *testing.T) {
   288  	checkEqual(t,
   289  		nil,
   290  		subtractSpans(
   291  			[]InvertedSpan{single("b")},
   292  			[]InvertedSpan{span("b", "b\x00")},
   293  		),
   294  	)
   295  	checkEqual(t,
   296  		[]InvertedSpan{span("b\x00", "d")},
   297  		subtractSpans(
   298  			[]InvertedSpan{span("b", "d")},
   299  			[]InvertedSpan{span("b", "b\x00")},
   300  		),
   301  	)
   302  	checkEqual(t,
   303  		[]InvertedSpan{span("b", "d"), span("e", "ea")},
   304  		subtractSpans(
   305  			[]InvertedSpan{span("b", "d"), span("e", "f")},
   306  			[]InvertedSpan{span("ea", "f")},
   307  		),
   308  	)
   309  	checkEqual(t,
   310  		[]InvertedSpan{span("d", "da"), span("da\x00", "dc"),
   311  			span("dd", "df"), span("fa", "g")},
   312  		subtractSpans(
   313  			[]InvertedSpan{single("b"), span("d", "e"), span("f", "g")},
   314  			[]InvertedSpan{span("b", "b\x00"), single("da"),
   315  				span("dc", "dd"), span("df", "e"), span("f", "fa")},
   316  		),
   317  	)
   318  
   319  }