Skip to content

Commit dbccfa9

Browse files
yuzefovichclaude
andcommitted
opt: fix panic with empty column sets in set operations
Fix panic "column statistics cannot be determined for empty column set" when performing set operations (UNION, INTERSECT, EXCEPT) on tables with no columns (e.g., created with "SELECT ALL"). Relatedly, relax the "invalid union" assertion in CanConvertUnionToDistinctUnionAll that incorrectly assumed that we will never have an operation with no columns. Fixes #130593 Release note: None 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 47465f9 commit dbccfa9

File tree

5 files changed

+66
-8
lines changed

5 files changed

+66
-8
lines changed

pkg/sql/execinfrapb/processors_sql.proto

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ message DistinctSpec {
407407
// check for distinct rows. If A,B,C are in distinct_columns and there is a
408408
// 4th column D which is not included in distinct_columns, its values are not
409409
// considered, so rows A1,B1,C1,D1 and A1,B1,C1,D2 are considered equal and
410-
// only one of them (the first) is output.
410+
// only one of them (the first) is output. distinct_columns can be empty
411+
// (which means that all rows are considered equal).
411412
repeated uint32 distinct_columns = 2;
412413
// If true, then NULL values are treated as not equal to one another. Each NULL
413414
// value will cause a new row group to be created. For example:

pkg/sql/logictest/testdata/logic_test/union

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,3 +750,52 @@ query II
750750
SELECT k, v FROM v127043_2 INNER JOIN t127043_3 ON k = k3 WHERE k = 1 LIMIT 1;
751751
----
752752
1 1
753+
754+
# Regression tests for #130593 (set operations with empty column sets).
755+
statement ok
756+
CREATE TABLE t130591_1 AS SELECT ALL;
757+
CREATE TABLE t130591_2 AS SELECT ALL
758+
759+
statement count 0
760+
TABLE t130591_1 EXCEPT DISTINCT TABLE t130591_1;
761+
762+
statement count 1
763+
TABLE t130591_1 INTERSECT DISTINCT TABLE t130591_1;
764+
765+
statement count 1
766+
TABLE t130591_1 UNION DISTINCT TABLE t130591_1;
767+
768+
statement count 0
769+
TABLE t130591_1 EXCEPT DISTINCT TABLE t130591_2;
770+
771+
statement count 1
772+
TABLE t130591_1 INTERSECT DISTINCT TABLE t130591_2;
773+
774+
statement count 1
775+
TABLE t130591_1 UNION DISTINCT TABLE t130591_2;
776+
777+
statement ok
778+
INSERT INTO t130591_1 DEFAULT VALUES;
779+
780+
query I
781+
SELECT count(*) FROM t130591_1;
782+
----
783+
2
784+
785+
statement count 0
786+
TABLE t130591_1 EXCEPT DISTINCT TABLE t130591_1;
787+
788+
statement count 1
789+
TABLE t130591_1 INTERSECT DISTINCT TABLE t130591_1;
790+
791+
statement count 1
792+
TABLE t130591_1 UNION DISTINCT TABLE t130591_1;
793+
794+
statement count 0
795+
TABLE t130591_1 EXCEPT TABLE t130591_1;
796+
797+
statement count 1
798+
TABLE t130591_1 INTERSECT TABLE t130591_1;
799+
800+
statement count 1
801+
TABLE t130591_1 UNION TABLE t130591_1;

pkg/sql/opt/memo/statistics_builder.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,8 +2193,16 @@ func (sb *statisticsBuilder) buildSetNode(setNode RelExpr, relProps *props.Relat
21932193
// count will equal the distinct count of the set of output columns.
21942194
setPrivate := setNode.Private().(*SetPrivate)
21952195
outputCols := setPrivate.OutCols.ToSet()
2196-
colStat := sb.colStatSetNodeImpl(outputCols, setNode, relProps)
2197-
s.RowCount = colStat.DistinctCount
2196+
if outputCols.Empty() {
2197+
// When there are no output columns (e.g., SELECT ALL), the distinct
2198+
// count is either 0 or 1, so we use the already computed row count.
2199+
if s.RowCount > 0 {
2200+
s.RowCount = 1
2201+
}
2202+
} else {
2203+
colStat := sb.colStatSetNodeImpl(outputCols, setNode, relProps)
2204+
s.RowCount = colStat.DistinctCount
2205+
}
21982206
}
21992207

22002208
sb.finalizeFromCardinality(relProps)

pkg/sql/opt/norm/set_funcs.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,13 @@ func (c *CustomFuncs) PruneSetPrivate(needed opt.ColSet, set *memo.SetPrivate) *
103103
func (c *CustomFuncs) CanConvertUnionToDistinctUnionAll(
104104
leftCols, rightCols opt.ColList,
105105
) (keyCols opt.ColSet, ok bool) {
106-
if len(leftCols) != len(rightCols) || len(leftCols) == 0 {
106+
if len(leftCols) != len(rightCols) {
107107
panic(errors.AssertionFailedf("invalid union"))
108108
}
109+
if len(leftCols) == 0 {
110+
// We must have some columns so that we have a non-empty key.
111+
return opt.ColSet{}, false
112+
}
109113
md := c.mem.Metadata()
110114
leftTableID, rightTableID := md.ColumnMeta(leftCols[0]).Table, md.ColumnMeta(rightCols[0]).Table
111115
if leftTableID == opt.TableID(0) || rightTableID == opt.TableID(0) {

pkg/sql/rowexec/distinct.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ func newDistinct(
7575
input execinfra.RowSource,
7676
post *execinfrapb.PostProcessSpec,
7777
) (execinfra.RowSourcedProcessor, error) {
78-
if len(spec.DistinctColumns) == 0 {
79-
return nil, errors.AssertionFailedf("0 distinct columns specified for distinct processor")
80-
}
81-
8278
nonOrderedCols := make([]uint32, 0, len(spec.DistinctColumns)-len(spec.OrderedColumns))
8379
for _, col := range spec.DistinctColumns {
8480
ordered := false

0 commit comments

Comments
 (0)