Skip to content

Commit

Permalink
opt: fixup CTE stats on placeholder queries
Browse files Browse the repository at this point in the history
During optbuilder phase we copy the initial expressions stats into the
fake-rel but this value can change when placeholders are assigned so add
a normalization step to do this so it happens on the assignplaceholder
path.

Fixes: cockroachdb#99389
Epic: none
Release note: none
  • Loading branch information
cucaroach committed Mar 24, 2023
1 parent ea389ff commit 9c807dc
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
17 changes: 17 additions & 0 deletions pkg/sql/opt/norm/rules/with.opt
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,20 @@
)
$private
)

# ApplyBindingRowCountToCTE makes sure to that stats changes are propagated on
# placeholder assignment.
[ApplyBindingRowCountToCTE, Normalize]
(RecursiveCTE
$binding:*
$initial:* & (RowCountDifferent $binding $initial)
$recursive:*
$private:*
)
=>
(RecursiveCTE
(ApplyBindingRowCount $binding $initial)
$initial
$recursive
$private
)
20 changes: 20 additions & 0 deletions pkg/sql/opt/norm/with_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,23 @@ func (c *CustomFuncs) CanAddRecursiveLimit(
func (c *CustomFuncs) GetRecursiveWithID(private *memo.RecursiveCTEPrivate) opt.WithID {
return private.WithID
}

func (c *CustomFuncs) ApplyBindingRowCount(
binding, initial memo.RelExpr,
) memo.RelExpr {
// The properties of the binding are tricky: the recursive expression is
// invoked repeatedly and these must hold each time. We can't use the initial
// expression's properties directly, as those only hold the first time the
// recursive query is executed. We don't really know the input row count,
// except for the first time we run the recursive query. We don't have
// anything better though.
initialRowCount := initial.Relational().Statistics().RowCount
binding.Relational().Statistics().RowCount = initialRowCount
return binding
}

func (c *CustomFuncs) RowCountDifferent(
binding, initial memo.RelExpr,
) bool {
return initial.Relational().Statistics().RowCount != binding.Relational().Statistics().RowCount
}
57 changes: 57 additions & 0 deletions pkg/sql/opt/xform/testdata/rules/cte
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
exec-ddl
CREATE TABLE xy (x INT, y INT, INDEX (x) STORING (y));
----

exec-ddl
ALTER TABLE xy INJECT STATISTICS '[
{
"columns": ["x"],
"created_at": "2018-01-01 1:00:00.00000+00:00",
"row_count": 100000,
"distinct_count": 100000
},
{
"columns": ["y"],
"created_at": "2018-01-01 1:00:00.00000+00:00",
"row_count": 100000,
"distinct_count": 2
}
]';
----

# Regression test for #99389.
assign-placeholders-opt query-args=(1) format=show-stats
WITH RECURSIVE cte(a,b) AS (
(SELECT * FROM xy WHERE x = $1)
UNION ALL
(SELECT * FROM cte WHERE False)
)
SELECT * FROM cte;
----
project
├── columns: a:10 b:11
├── stats: [rows=10]
├── recursive-c-t-e
│ ├── columns: a:6 b:7
│ ├── working table binding: &1
│ ├── initial columns: x:1 y:2
│ ├── recursive columns: a:8 b:9
│ ├── stats: [rows=10]
│ ├── fake-rel
│ │ ├── columns: a:6 b:7
│ │ ├── cardinality: [1 - ]
│ │ └── stats: [rows=1.00001]
│ ├── scan xy@xy_x_idx
│ │ ├── columns: x:1!null y:2
│ │ ├── constraint: /1/3: [/1 - /1]
│ │ ├── stats: [rows=1.00001, distinct(1)=1, null(1)=0]
│ │ └── fd: ()-->(1)
│ └── values
│ ├── columns: a:8!null b:9!null
│ ├── cardinality: [0 - 0]
│ ├── stats: [rows=0]
│ ├── key: ()
│ └── fd: ()-->(8,9)
└── projections
├── a:6 [as=a:10, outer=(6)]
└── b:7 [as=b:11, outer=(7)]

0 comments on commit 9c807dc

Please sign in to comment.