diff --git a/pkg/sql/opt/ordering/project.go b/pkg/sql/opt/ordering/project.go index 62410404af24..b824cfba05ce 100644 --- a/pkg/sql/opt/ordering/project.go +++ b/pkg/sql/opt/ordering/project.go @@ -14,6 +14,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/opt" "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/opt/props" + "github.com/cockroachdb/errors" ) func projectCanProvideOrdering(expr memo.RelExpr, required *props.OrderingChoice) bool { @@ -74,13 +75,24 @@ func projectOrderingToInput( } func projectBuildProvided(expr memo.RelExpr, required *props.OrderingChoice) opt.Ordering { - p := expr.(*memo.ProjectExpr) + // Ensure that the child provided ordering only refers to columns from the + // required ordering choice. This is necessary because there may be cases + // where the input of the Project has undergone transformations that allow it + // to "see" more functional dependencies than the original memo group. This + // can cause the child to provide an ordering that is equivalent to the + // required ordering, but which the parent Project cannot prove is equivalent + // because its FDs have less information. This can lead to a panic later on. + ordCols := required.ColSet() + if !ordCols.SubsetOf(expr.Relational().OutputCols) { + panic(errors.AssertionFailedf("expected required columns to be a subset of output columns")) + } // Project can only satisfy required orderings that refer to projected // columns; it should always be possible to remap the columns in the input's // provided ordering. + p := expr.(*memo.ProjectExpr) return remapProvided( p.Input.ProvidedPhysical().Ordering, p.InternalFDs(), - p.Relational().OutputCols, + ordCols, ) } diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index edd6a08da9ea..bef88d892ab6 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -2511,3 +2511,41 @@ project │ ├── name:2 = name:8 [outer=(2,8), fd=(2)==(8), (8)==(2)] │ └── k:7::STRING = lower(name:8) [outer=(7,8), immutable] └── 56 + +# Regression test for #85393 - use only columns from the required ordering when +# building the provided ordering for Project operators. +exec-ddl +CREATE TABLE t0_85393 (c0 INT); +---- + +exec-ddl +CREATE TABLE t1_85393 (c0 INT); +---- + +opt +SELECT * +FROM t0_85393 CROSS JOIN t1_85393 +WHERE (t0_85393.rowid IS NULL) OR (t1_85393.rowid IN (t0_85393.rowid)) +ORDER BY t1_85393.rowid; +---- +sort + ├── columns: c0:1 c0:5 [hidden: t1_85393.rowid:6!null] + ├── fd: (6)-->(5) + ├── ordering: +6 + └── project + ├── columns: t0_85393.c0:1 t1_85393.c0:5 t1_85393.rowid:6!null + ├── fd: (6)-->(5) + └── inner-join (cross) + ├── columns: t0_85393.c0:1 t0_85393.rowid:2!null t1_85393.c0:5 t1_85393.rowid:6!null + ├── key: (2,6) + ├── fd: (2)-->(1), (6)-->(5) + ├── scan t0_85393 + │ ├── columns: t0_85393.c0:1 t0_85393.rowid:2!null + │ ├── key: (2) + │ └── fd: (2)-->(1) + ├── scan t1_85393 + │ ├── columns: t1_85393.c0:5 t1_85393.rowid:6!null + │ ├── key: (6) + │ └── fd: (6)-->(5) + └── filters + └── (t0_85393.rowid:2 IS NULL) OR (t1_85393.rowid:6 = t0_85393.rowid:2) [outer=(2,6)]