github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/props/physical/ordering_choice_test.go (about)

     1  // Copyright 2018 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 physical_test
    12  
    13  import (
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    17  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/opt/props/physical"
    19  )
    20  
    21  func TestOrderingChoice_FromOrdering(t *testing.T) {
    22  	var oc physical.OrderingChoice
    23  	oc.FromOrdering(opt.Ordering{1, -2, 3})
    24  	if exp, actual := "+1,-2,+3", oc.String(); exp != actual {
    25  		t.Errorf("expected %s, got %s", exp, actual)
    26  	}
    27  
    28  	oc.FromOrderingWithOptCols(opt.Ordering{1, -2, 3, 4, -5}, opt.MakeColSet(1, 3, 5))
    29  	if exp, actual := "-2,+4 opt(1,3,5)", oc.String(); exp != actual {
    30  		t.Errorf("expected %s, got %s", exp, actual)
    31  	}
    32  }
    33  
    34  func TestOrderingChoice_ToOrdering(t *testing.T) {
    35  	testcases := []struct {
    36  		s string
    37  		o opt.Ordering
    38  	}{
    39  		{s: " ", o: opt.Ordering{}},
    40  		{s: "+1", o: opt.Ordering{1}},
    41  		{s: "-1,+(2|3) opt(4,5)", o: opt.Ordering{-1, 2}},
    42  		{s: "+(1|2),-(3|4),+5", o: opt.Ordering{1, -3, 5}},
    43  	}
    44  
    45  	for _, tc := range testcases {
    46  		choice := physical.ParseOrderingChoice(tc.s)
    47  		ordering := choice.ToOrdering()
    48  		if len(ordering) != len(tc.o) {
    49  			t.Errorf("%s: expected %s, actual: %s", tc.s, tc.o, ordering)
    50  		} else {
    51  			for i := range ordering {
    52  				if ordering[i] != tc.o[i] {
    53  					t.Errorf("%s: expected %s, actual: %s", tc.s, tc.o, ordering)
    54  				}
    55  			}
    56  		}
    57  	}
    58  }
    59  
    60  func TestOrderingChoice_ColSet(t *testing.T) {
    61  	testcases := []struct {
    62  		s  string
    63  		cs opt.ColSet
    64  	}{
    65  		{s: "", cs: opt.MakeColSet()},
    66  		{s: "+1", cs: opt.MakeColSet(1)},
    67  		{s: "-1,+(2|3) opt(4,5)", cs: opt.MakeColSet(1, 2, 3)},
    68  		{s: "+(1|2),-(3|4),+5", cs: opt.MakeColSet(1, 2, 3, 4, 5)},
    69  	}
    70  
    71  	for _, tc := range testcases {
    72  		choice := physical.ParseOrderingChoice(tc.s)
    73  		colSet := choice.ColSet()
    74  		if !colSet.Equals(tc.cs) {
    75  			t.Errorf("%s: expected %s, actual: %s", tc.s, tc.cs, colSet)
    76  		}
    77  	}
    78  }
    79  
    80  func TestOrderingChoice_Implies(t *testing.T) {
    81  	testcases := []struct {
    82  		left     string
    83  		right    string
    84  		expected bool
    85  	}{
    86  		{left: "", right: "", expected: true},
    87  		{left: "+1", right: "", expected: true},
    88  		{left: "+1", right: "+1", expected: true},
    89  		{left: "+1,-2", right: "+1", expected: true},
    90  		{left: "+1,-2", right: "+1,-2", expected: true},
    91  		{left: "+1", right: "+1 opt(2)", expected: true},
    92  		{left: "-2,+1", right: "+1 opt(2)", expected: true},
    93  		{left: "+1", right: "+(1|2)", expected: true},
    94  		{left: "+(1|2)", right: "+(1|2|3)", expected: true},
    95  		{left: "+(1|2),-4", right: "+(1|2|3),-(4|5)", expected: true},
    96  		{left: "+(1|2) opt(4)", right: "+(1|2|3) opt(4)", expected: true},
    97  
    98  		{left: "", right: "+1", expected: false},
    99  		{left: "+1", right: "-1", expected: false},
   100  		{left: "+1", right: "-1,-2", expected: false},
   101  		{left: "+1 opt(2)", right: "+1", expected: false},
   102  		{left: "+1 opt(2)", right: "+1 opt(3)", expected: false},
   103  		{left: "+(1|2)", right: "-(1|2)", expected: false},
   104  		{left: "+(1|2)", right: "+(3|4)", expected: false},
   105  		{left: "+(1|2)", right: "+(2|3)", expected: false},
   106  		{left: "+(1|2|3)", right: "+(1|2)", expected: false},
   107  		{left: "+(1|2)", right: "+1 opt(2)", expected: false},
   108  		{left: "+(1|2),-(3|4)", right: "+(1|2),-(3|4),+5", expected: false},
   109  		{left: "+1", right: "+3 opt(1,2)", expected: false},
   110  		{left: "+3 opt(1,2)", right: "+1", expected: false},
   111  	}
   112  
   113  	for _, tc := range testcases {
   114  		left := physical.ParseOrderingChoice(tc.left)
   115  		right := physical.ParseOrderingChoice(tc.right)
   116  		if left.Implies(&right) != tc.expected {
   117  			if tc.expected {
   118  				t.Errorf("expected %s to imply %s", tc.left, tc.right)
   119  			} else {
   120  				t.Errorf("expected %s to not imply %s", tc.left, tc.right)
   121  			}
   122  		}
   123  	}
   124  }
   125  
   126  // TestOrderingChoice_Intersection tests Intersects and Intersection.
   127  func TestOrderingChoice_Intersection(t *testing.T) {
   128  	testcases := []struct {
   129  		left           string
   130  		right          string
   131  		expected       string
   132  		nonCommutative bool
   133  	}{
   134  		{left: "", right: "", expected: ""},
   135  		{left: "+1", right: "", expected: "+1"},
   136  		{left: "+1 opt(2)", right: "", expected: "+1 opt(2)"},
   137  		{left: "+1", right: "+1", expected: "+1"},
   138  		{left: "+1,-2", right: "+1", expected: "+1,-2"},
   139  		{left: "+1,-2", right: "+1,-2", expected: "+1,-2"},
   140  		{left: "+1", right: "+1 opt(2)", expected: "+1"},
   141  		{left: "+1", right: "+2 opt(1)", expected: "+1,+2"},
   142  		{left: "-2,+1", right: "+1 opt(2)", expected: "-2,+1"},
   143  		{left: "+1", right: "+(1|2)", expected: "+1"},
   144  		{left: "+(1|2)", right: "+(1|2|3)", expected: "+(1|2)"},
   145  		{left: "+(1|2),-4", right: "+(1|2|3),-(4|5)", expected: "+(1|2),-4"},
   146  		{left: "+(1|2) opt(4)", right: "+(1|2|3) opt(4)", expected: "+(1|2) opt(4)"},
   147  
   148  		{left: "+1 opt(2,3,4)", right: "+1 opt(4,5)", expected: "+1 opt(4)"},
   149  		{left: "+1 opt(2,3,4)", right: "+1 opt(4,5)", expected: "+1 opt(4)"},
   150  		{left: "+1,+4,+5", right: "+4,+5 opt(1)", expected: "+1,+4,+5"},
   151  		{left: "+(1|2),+(3|4)", right: "+(2|3),+(4|5)", expected: "+2,+4"},
   152  		{left: "+(1|2|3),+(4|5)", right: "+(2|3),+(4|5|6)", expected: "+(2|3),+(4|5)"},
   153  
   154  		{left: "+1", right: "+2", expected: "NO"},
   155  		{left: "+1", right: "+2 opt(2)", expected: "NO"},
   156  		{left: "+1", right: "-1 opt(2)", expected: "NO"},
   157  		{left: "+(1|2),+(3|4)", right: "+(2|5),+(6|7)", expected: "NO"},
   158  
   159  		// Non-commutative cases.
   160  		{
   161  			left:           "+1 opt(2,5)",
   162  			right:          "+2 opt(1,5)",
   163  			expected:       "+1,+2 opt(5)",
   164  			nonCommutative: true,
   165  		},
   166  		{
   167  			left:           "+2 opt(1,5)",
   168  			right:          "+1 opt(2,5)",
   169  			expected:       "+2,+1 opt(5)",
   170  			nonCommutative: true,
   171  		},
   172  		{
   173  			left:           "+(1|2),+(3|4) opt(6)",
   174  			right:          "+(2|3),+(5|6) opt(4)",
   175  			expected:       "+2,+4,+(5|6)",
   176  			nonCommutative: true,
   177  		},
   178  		{
   179  			left:           "+(2|3),+(5|6) opt(4)",
   180  			right:          "+(1|2),+(3|4) opt(6)",
   181  			expected:       "+2,+6,+(3|4)",
   182  			nonCommutative: true,
   183  		},
   184  		{
   185  			left:           "+(1|2|3),-(4|5|6) opt(7)",
   186  			right:          "-7 opt(2,3,5,6)",
   187  			expected:       "+(2|3),-(5|6),-7",
   188  			nonCommutative: true,
   189  		},
   190  		{
   191  			left:           "-7 opt(2,3,5,6)",
   192  			right:          "+(1|2|3),-(4|5|6) opt(7)",
   193  			expected:       "-7,+(1|2|3),-(4|5|6)",
   194  			nonCommutative: true,
   195  		},
   196  	}
   197  
   198  	getRes := func(left, right physical.OrderingChoice) string {
   199  		if !left.Intersects(&right) {
   200  			return "NO"
   201  		}
   202  		return left.Intersection(&right).String()
   203  	}
   204  
   205  	for _, tc := range testcases {
   206  		left := physical.ParseOrderingChoice(tc.left)
   207  		right := physical.ParseOrderingChoice(tc.right)
   208  
   209  		res := getRes(left, right)
   210  		if res != tc.expected {
   211  			t.Errorf(
   212  				"intersection between '%s' and '%s': expected '%s', got '%s'",
   213  				left, right, tc.expected, res,
   214  			)
   215  		}
   216  		if !tc.nonCommutative {
   217  			if res2 := getRes(right, left); res2 != res {
   218  				t.Errorf(
   219  					"intersection not commutative: left='%s' right='%s': '%s' vs '%s'",
   220  					left, right, res, res2,
   221  				)
   222  			}
   223  		}
   224  	}
   225  }
   226  
   227  func TestOrderingChoice_SubsetOfCols(t *testing.T) {
   228  	testcases := []struct {
   229  		s        string
   230  		cs       opt.ColSet
   231  		expected bool
   232  	}{
   233  		{s: "", cs: opt.MakeColSet(), expected: true},
   234  		{s: "", cs: opt.MakeColSet(1), expected: true},
   235  		{s: "+1", cs: opt.MakeColSet(1), expected: true},
   236  		{s: "-1", cs: opt.MakeColSet(1, 2), expected: true},
   237  		{s: "+1 opt(2)", cs: opt.MakeColSet(1), expected: false},
   238  		{s: "+1 opt(2)", cs: opt.MakeColSet(1, 2), expected: true},
   239  		{s: "+(1|2)", cs: opt.MakeColSet(1, 2, 3), expected: true},
   240  		{s: "+(1|2)", cs: opt.MakeColSet(2), expected: false},
   241  		{s: "+1,-(2|3),-4 opt(4,5)", cs: opt.MakeColSet(1, 3, 4), expected: false},
   242  		{s: "+1,-(2|3),-4 opt(4,5)", cs: opt.MakeColSet(1, 2, 3, 4), expected: false},
   243  		{s: "+1,-(2|3),-4 opt(4,5)", cs: opt.MakeColSet(1, 2, 3, 4, 5), expected: true},
   244  	}
   245  
   246  	for _, tc := range testcases {
   247  		choice := physical.ParseOrderingChoice(tc.s)
   248  		if choice.SubsetOfCols(tc.cs) != tc.expected {
   249  			if tc.expected {
   250  				t.Errorf("%s: expected cols to be subset of %s", tc.s, tc.cs)
   251  			} else {
   252  				t.Errorf("%s: expected cols to not be subset of %s", tc.s, tc.cs)
   253  			}
   254  		}
   255  	}
   256  }
   257  
   258  func TestOrderingChoice_CanProjectCols(t *testing.T) {
   259  	testcases := []struct {
   260  		s        string
   261  		cs       opt.ColSet
   262  		expected bool
   263  	}{
   264  		{s: "", cs: opt.MakeColSet(), expected: true},
   265  		{s: "", cs: opt.MakeColSet(1), expected: true},
   266  		{s: "+1", cs: opt.MakeColSet(1), expected: true},
   267  		{s: "-1", cs: opt.MakeColSet(1, 2), expected: true},
   268  		{s: "+1 opt(2)", cs: opt.MakeColSet(1), expected: true},
   269  		{s: "+(1|2)", cs: opt.MakeColSet(1), expected: true},
   270  		{s: "+(1|2)", cs: opt.MakeColSet(2), expected: true},
   271  		{s: "+1,-(2|3),-4 opt(4,5)", cs: opt.MakeColSet(1, 3, 4), expected: true},
   272  
   273  		{s: "+1", cs: opt.MakeColSet(), expected: false},
   274  		{s: "+1,+2", cs: opt.MakeColSet(1), expected: false},
   275  		{s: "+(1|2)", cs: opt.MakeColSet(3), expected: false},
   276  	}
   277  
   278  	for _, tc := range testcases {
   279  		choice := physical.ParseOrderingChoice(tc.s)
   280  		if choice.CanProjectCols(tc.cs) != tc.expected {
   281  			if tc.expected {
   282  				t.Errorf("%s: expected CanProject(%s)", tc.s, tc.cs)
   283  			} else {
   284  				t.Errorf("%s: expected !CanProject(%s)", tc.s, tc.cs)
   285  			}
   286  		}
   287  	}
   288  }
   289  
   290  func TestOrderingChoice_MatchesAt(t *testing.T) {
   291  	s1 := "+1"
   292  	s2 := "+1,-2 opt(3,4)"
   293  	s3 := "+(1|2)"
   294  	s4 := "+(1|2),-3,+(4|5) opt(6,7)"
   295  
   296  	testcases := []struct {
   297  		s        string
   298  		idx      int
   299  		col      opt.ColumnID
   300  		desc     bool
   301  		expected bool
   302  	}{
   303  		{s: s1, idx: 0, col: 1, desc: false, expected: true},
   304  		{s: s1, idx: 0, col: 1, desc: true, expected: false},
   305  		{s: s1, idx: 0, col: 2, desc: false, expected: false},
   306  
   307  		{s: s2, idx: 0, col: 1, desc: false, expected: true},
   308  		{s: s2, idx: 1, col: 2, desc: true, expected: true},
   309  		{s: s2, idx: 1, col: 2, desc: false, expected: false},
   310  		{s: s2, idx: 1, col: 3, desc: false, expected: true},
   311  		{s: s2, idx: 0, col: 4, desc: false, expected: true},
   312  
   313  		{s: s3, idx: 0, col: 1, desc: false, expected: true},
   314  		{s: s3, idx: 0, col: 2, desc: false, expected: true},
   315  		{s: s3, idx: 0, col: 2, desc: true, expected: false},
   316  		{s: s3, idx: 0, col: 3, desc: false, expected: false},
   317  
   318  		{s: s4, idx: 0, col: 6, desc: false, expected: true},
   319  		{s: s4, idx: 1, col: 3, desc: true, expected: true},
   320  		{s: s4, idx: 2, col: 5, desc: false, expected: true},
   321  		{s: s4, idx: 2, col: 7, desc: true, expected: true},
   322  	}
   323  
   324  	for _, tc := range testcases {
   325  		ordering := physical.ParseOrderingChoice(tc.s)
   326  		ordCol := opt.MakeOrderingColumn(tc.col, tc.desc)
   327  		if ordering.MatchesAt(tc.idx, ordCol) != tc.expected {
   328  			if tc.expected {
   329  				t.Errorf("expected %s to match at index %d: %s", ordCol, tc.idx, tc.s)
   330  			} else {
   331  				t.Errorf("expected %s not to match at index %d: %s", ordCol, tc.idx, tc.s)
   332  			}
   333  		}
   334  	}
   335  }
   336  
   337  func TestOrderingChoice_Copy(t *testing.T) {
   338  	ordering := physical.ParseOrderingChoice("+1,-(2|3) opt(4,5)")
   339  	copied := ordering.Copy()
   340  	col := physical.OrderingColumnChoice{Group: opt.MakeColSet(6, 7), Descending: true}
   341  	copied.Columns = append(copied.Columns, col)
   342  
   343  	// ()-->(8)
   344  	// (3)==(9)
   345  	// (9)==(3)
   346  	var fd props.FuncDepSet
   347  	fd.AddConstants(opt.MakeColSet(8))
   348  	fd.AddEquivalency(3, 9)
   349  	copied.Simplify(&fd)
   350  
   351  	if ordering.String() != "+1,-(2|3) opt(4,5)" {
   352  		t.Errorf("original was modified: %s", ordering.String())
   353  	}
   354  
   355  	if copied.String() != "+1,-(2|3|9),-(6|7) opt(4,5,8)" {
   356  		t.Errorf("copy is not correct: %s", copied.String())
   357  	}
   358  }
   359  
   360  func TestOrderingChoice_Simplify(t *testing.T) {
   361  	// ()-->(4,5)
   362  	// (1)==(1,3)
   363  	// (2)==(1)
   364  	// (3)==(1)
   365  	var fd1 props.FuncDepSet
   366  	fd1.AddConstants(opt.MakeColSet(4, 5))
   367  	fd1.AddEquivalency(1, 2)
   368  	fd1.AddEquivalency(1, 3)
   369  
   370  	// (1)-->(1,2,3,4,5)
   371  	// (2)-->(4)
   372  	// (4)-->(5)
   373  	// (2)==(3)
   374  	// (3)==(2)
   375  	var fd2 props.FuncDepSet
   376  	fd2.AddStrictKey(opt.MakeColSet(1), opt.MakeColSet(1, 2, 3, 4, 5))
   377  	fd2.AddSynthesizedCol(opt.MakeColSet(2), 4)
   378  	fd2.AddSynthesizedCol(opt.MakeColSet(4), 5)
   379  	fd2.AddEquivalency(2, 3)
   380  
   381  	testcases := []struct {
   382  		fdset    *props.FuncDepSet
   383  		s        string
   384  		expected string
   385  	}{
   386  		{fdset: &props.FuncDepSet{}, s: "", expected: ""},
   387  
   388  		// Constants and equivalencies.
   389  		{fdset: &fd1, s: "", expected: ""},
   390  		{fdset: &fd1, s: "+1,+4", expected: "+(1|2|3) opt(4,5)"},
   391  		{fdset: &fd1, s: "+2,+4", expected: "+(1|2|3) opt(4,5)"},
   392  		{fdset: &fd1, s: "+1,+2 opt(4)", expected: "+(1|2|3) opt(4,5)"},
   393  		{fdset: &fd1, s: "+1,+2 opt(4)", expected: "+(1|2|3) opt(4,5)"},
   394  		{fdset: &fd1, s: "+(4|5)", expected: ""},
   395  		{fdset: &fd1, s: "+(4|5) opt(3)", expected: ""},
   396  		{fdset: &fd1, s: "+(4|5|6)", expected: "+6 opt(4,5)"},
   397  		{fdset: &fd1, s: "+(4|6)", expected: "+6 opt(4,5)"},
   398  
   399  		// Columns functionally determine one another.
   400  		{fdset: &fd2, s: "", expected: ""},
   401  		{fdset: &fd2, s: "+1,+2,+4", expected: "+1"},
   402  		{fdset: &fd2, s: "+2,+4,+5", expected: "+(2|3)"},
   403  		{fdset: &fd2, s: "+3,+5", expected: "+(2|3)"},
   404  		{fdset: &fd2, s: "-(2|3),+1,+5", expected: "-(2|3),+1"},
   405  		{fdset: &fd2, s: "-(2|4),+5,+1", expected: "-(2|3|4),+1"},
   406  	}
   407  
   408  	for _, tc := range testcases {
   409  		ordering := physical.ParseOrderingChoice(tc.s)
   410  
   411  		if ordering.String() != tc.expected && !ordering.CanSimplify(tc.fdset) {
   412  			t.Errorf("%s: expected CanSimplify to be true", tc.s)
   413  		}
   414  
   415  		ordering.Simplify(tc.fdset)
   416  		if ordering.String() != tc.expected {
   417  			t.Errorf("%s: expected %s, actual %s", tc.s, tc.expected, ordering.String())
   418  		}
   419  
   420  		if ordering.CanSimplify(tc.fdset) {
   421  			t.Errorf("%s: expected CanSimplify to be false", ordering.String())
   422  		}
   423  	}
   424  }
   425  
   426  func TestOrderingChoice_Truncate(t *testing.T) {
   427  	testcases := []struct {
   428  		s        string
   429  		n        int
   430  		expected string
   431  	}{
   432  		{s: "", n: 0, expected: ""},
   433  		{s: "", n: 1, expected: ""},
   434  		{s: "+1,+(2|3),-4 opt(5,6)", n: 0, expected: ""},
   435  		{s: "+1,+(2|3),-4 opt(5,6)", n: 1, expected: "+1 opt(5,6)"},
   436  		{s: "+1,+(2|3),-4 opt(5,6)", n: 2, expected: "+1,+(2|3) opt(5,6)"},
   437  		{s: "+1,+(2|3),-4 opt(5,6)", n: 3, expected: "+1,+(2|3),-4 opt(5,6)"},
   438  		{s: "+1,+(2|3),-4 opt(5,6)", n: 4, expected: "+1,+(2|3),-4 opt(5,6)"},
   439  	}
   440  
   441  	for _, tc := range testcases {
   442  		choice := physical.ParseOrderingChoice(tc.s)
   443  		choice.Truncate(tc.n)
   444  		if choice.String() != tc.expected {
   445  			t.Errorf("%s: n=%d, expected: %s, actual: %s", tc.s, tc.n, tc.expected, choice.String())
   446  		}
   447  	}
   448  }
   449  
   450  func TestOrderingChoice_ProjectCols(t *testing.T) {
   451  	testcases := []struct {
   452  		s        string
   453  		cols     []opt.ColumnID
   454  		expected string
   455  	}{
   456  		{s: "", cols: []opt.ColumnID{}, expected: ""},
   457  		{s: "+1,+(2|3),-4 opt(5,6)", cols: []opt.ColumnID{1, 2, 3, 4, 5, 6}, expected: "+1,+(2|3),-4 opt(5,6)"},
   458  		{s: "+1,+(2|3),-4 opt(5,6)", cols: []opt.ColumnID{1, 2, 4, 5, 6}, expected: "+1,+2,-4 opt(5,6)"},
   459  		{s: "+1,+(2|3),-4 opt(5,6)", cols: []opt.ColumnID{1, 3, 4, 5, 6}, expected: "+1,+3,-4 opt(5,6)"},
   460  		{s: "+1,+(2|3),-4 opt(5,6)", cols: []opt.ColumnID{1, 2, 4, 5}, expected: "+1,+2,-4 opt(5)"},
   461  		{s: "+1,+(2|3),-4 opt(5,6)", cols: []opt.ColumnID{1, 2, 4}, expected: "+1,+2,-4"},
   462  	}
   463  
   464  	for _, tc := range testcases {
   465  		choice := physical.ParseOrderingChoice(tc.s)
   466  		choice.ProjectCols(opt.MakeColSet(tc.cols...))
   467  		if choice.String() != tc.expected {
   468  			t.Errorf("%s: cols=%v, expected: %s, actual: %s", tc.s, tc.cols, tc.expected, choice.String())
   469  		}
   470  	}
   471  }
   472  
   473  func TestOrderingChoice_Equals(t *testing.T) {
   474  	testcases := []struct {
   475  		left     string
   476  		right    string
   477  		expected bool
   478  	}{
   479  		{left: "", right: "", expected: true},
   480  		{left: "+1", right: "+1", expected: true},
   481  		{left: "+1,+2", right: "+1,+2", expected: true},
   482  		{left: "+(1|2)", right: "+(2|1)", expected: true},
   483  		{left: "+(1|2),+3", right: "+(2|1),+3", expected: true},
   484  		{left: "+(1|2),-(3|4) opt(5,6)", right: "+(2|1),-(4|3) opt(6,5)", expected: true},
   485  
   486  		{left: "+1", right: "", expected: false},
   487  		{left: "+1", right: "-1", expected: false},
   488  		{left: "+1", right: "+2", expected: false},
   489  		{left: "+1,+2", right: "+2,+1", expected: false},
   490  		{left: "+1 opt(2)", right: "+1 opt(2,3)", expected: false},
   491  	}
   492  
   493  	for _, tc := range testcases {
   494  		left := physical.ParseOrderingChoice(tc.left)
   495  		right := physical.ParseOrderingChoice(tc.right)
   496  		if left.Equals(&right) != tc.expected {
   497  			if tc.expected {
   498  				t.Errorf("expected %s to equal %s", tc.left, tc.right)
   499  			} else {
   500  				t.Errorf("expected %s to not equal %s", tc.left, tc.right)
   501  			}
   502  		}
   503  	}
   504  }
   505  
   506  func TestOrderingChoice_PrefixIntersection(t *testing.T) {
   507  	testcases := []struct {
   508  		x        string
   509  		prefix   opt.ColList
   510  		y        string
   511  		expected string
   512  	}{
   513  		{x: "+1", prefix: opt.ColList{}, y: "+2", expected: "fail"},
   514  		{x: "", prefix: opt.ColList{}, y: "+1", expected: "+1"},
   515  		{x: "", prefix: opt.ColList{}, y: "+1,+2", expected: "+1,+2"},
   516  		{x: "+(1|2)", prefix: opt.ColList{}, y: "+(1|2)", expected: "+(1|2)"},
   517  		{x: "+(1|2)", prefix: opt.ColList{}, y: "+1", expected: "+1"},
   518  		{x: "+(1|2)", prefix: opt.ColList{}, y: "+2", expected: "+2"},
   519  		{x: "+1,+2", prefix: opt.ColList{3}, y: "", expected: "fail"},
   520  		{x: "", prefix: opt.ColList{3}, y: "+1,+2", expected: "+3,+1,+2"},
   521  		{x: "+1,+2", prefix: opt.ColList{3, 4}, y: "", expected: "fail"},
   522  		{x: "", prefix: opt.ColList{3, 4}, y: "+1,+2", expected: "+3,+4,+1,+2"},
   523  		{x: "+1", prefix: opt.ColList{}, y: "+1", expected: "+1"},
   524  		{x: "+1,+2", prefix: opt.ColList{}, y: "+1", expected: "+1,+2"},
   525  		{x: "+1", prefix: opt.ColList{}, y: "+1,+2", expected: "+1,+2"},
   526  		{x: "+1,+2", prefix: opt.ColList{}, y: "+1,+2", expected: "+1,+2"},
   527  		{x: "+1,+2", prefix: opt.ColList{1}, y: "+2", expected: "+1,+2"},
   528  		{x: "+2", prefix: opt.ColList{3}, y: "+1,+2", expected: "fail"},
   529  		{x: "+1,+2", prefix: opt.ColList{}, y: "+1,+2", expected: "+1,+2"},
   530  		{x: "+1,+2", prefix: opt.ColList{1}, y: "+2,+3", expected: "+1,+2,+3"},
   531  		{x: "+1,+2", prefix: opt.ColList{1, 2}, y: "+3", expected: "+1,+2,+3"},
   532  		{x: "+1,+2", prefix: opt.ColList{1, 2}, y: "", expected: "+1,+2"},
   533  		{x: "+2,+1", prefix: opt.ColList{1, 2}, y: "", expected: "+2,+1"},
   534  		{x: "+2,+3", prefix: opt.ColList{3}, y: "+1,+2", expected: "fail"},
   535  		{x: "", prefix: opt.ColList{1, 2}, y: "", expected: "+1,+2"},
   536  		{x: "", prefix: opt.ColList{2}, y: "+3 opt(2)", expected: "+2,+3"},
   537  		{x: "", prefix: opt.ColList{2}, y: "+3", expected: "+2,+3"},
   538  	}
   539  
   540  	for _, tc := range testcases {
   541  		left := physical.ParseOrderingChoice(tc.x)
   542  		right := physical.ParseOrderingChoice(tc.y)
   543  
   544  		cols := tc.prefix.ToSet()
   545  
   546  		result, ok := left.PrefixIntersection(cols, right.Columns)
   547  		s := "fail"
   548  		if ok {
   549  			s = result.String()
   550  		}
   551  
   552  		if s != tc.expected {
   553  			t.Errorf("%q.PrefixIntersection(%q, %q): expected %q, got %q", left, cols, right, tc.expected, s)
   554  		}
   555  	}
   556  }