Skip to content

Commit d669516

Browse files
craig[bot]yuzefovichmgartner
committed
156813: sql: wrap CASE expressions with parenthesis within PLpgSQL context r=yuzefovich a=yuzefovich Currently, if we generate a CASE expression within the condition of the IF block in PLpgSQL, it cannot be parsed because it's unclear whether THEN belongs to CASE or to IF. In order to disambiguate this, we need to wrap CASE expressions in parenthesis, and we now do so in all sqlsmith-generated queries whenever we're in PLpgSQL context. Fixes: #155200. Release note: None 156819: CODEOWNERS: remove sql-syntax-prs team r=yuzefovich a=yuzefovich Divide ownership between Queries and Foundations. Epic: None Release note: None 156879: opt: avoid more types of bad generic query plans r=mgartner a=mgartner This commit extends #155163. Instead of tracking an auxiliary boolean value to indicate that a plan has one or more expressions with unbounded cardinality, the `memo.Cost` struct now tracks the number of read expressions (i.e., expressions that perform KV reads) that have unbounded cardinality. This helps to avoid picking generic query plans that will perform significantly worse than their related custom query plans. Fixes #156690 Release note: None Co-authored-by: Yahor Yuzefovich <yahor@cockroachlabs.com> Co-authored-by: Marcus Gartner <marcus@cockroachlabs.com>
4 parents aa90415 + 10a81e5 + b984530 + e318d31 commit d669516

File tree

14 files changed

+185
-92
lines changed

14 files changed

+185
-92
lines changed

.github/CODEOWNERS

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,11 @@
7171
/pkg/sql/executor_statement_metrics.go @cockroachdb/obs-prs
7272
/pkg/sql/flowinfra/ @cockroachdb/sql-queries-prs
7373
/pkg/sql/hints/ @cockroachdb/sql-queries-prs
74+
/pkg/sql/parser/ @cockroachdb/sql-queries-prs
7475
/pkg/sql/physicalplan/ @cockroachdb/sql-queries-prs
7576
/pkg/sql/row* @cockroachdb/sql-queries-prs
77+
/pkg/sql/sem/tree/ @cockroachdb/sql-queries-prs
78+
/pkg/sql/types/ @cockroachdb/sql-queries-prs
7679
/pkg/sql/control_job* @cockroachdb/sql-queries-prs @cockroachdb/jobs-prs
7780
/pkg/sql/job_exec_context* @cockroachdb/sql-queries-prs @cockroachdb/jobs-prs
7881
/pkg/sql/delegate/*job*.go @cockroachdb/jobs-prs @cockroachdb/disaster-recovery
@@ -92,14 +95,10 @@
9295
/pkg/sql/tablemetadatacache/ @cockroachdb/obs-prs
9396
/pkg/ccl/testccl/sqlstatsccl/ @cockroachdb/obs-prs
9497

95-
/pkg/sql/sem/tree/ @cockroachdb/sql-syntax-prs
96-
/pkg/sql/parser/ @cockroachdb/sql-syntax-prs
97-
/pkg/sql/lex/ @cockroachdb/sql-syntax-prs
98-
/pkg/sql/show_create*.go @cockroachdb/sql-syntax-prs
99-
/pkg/sql/types/ @cockroachdb/sql-syntax-prs
100-
10198
/pkg/sql/crdb_internal.go @cockroachdb/sql-foundations
10299
/pkg/sql/pg_catalog.go @cockroachdb/sql-foundations
100+
/pkg/sql/show_create*.go @cockroachdb/sql-foundations
101+
/pkg/sql/lex/ @cockroachdb/sql-foundations
103102
/pkg/sql/pgwire/ @cockroachdb/sql-foundations @cockroachdb/product-security
104103
/pkg/sql/pgwire/auth.go @cockroachdb/sql-foundations @cockroachdb/security-engineering
105104
/pkg/sql/sem/builtins/ @cockroachdb/sql-foundations

TEAMS.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ cockroachdb/docs:
77
cockroachdb/docs-infra-prs: other
88
cockroachdb/sql-foundations:
99
aliases:
10-
cockroachdb/sql-syntax-prs: other
1110
cockroachdb/sqlproxy-prs: other
1211
cockroachdb/sql-api-prs: other
1312
label: T-sql-foundations

pkg/internal/sqlsmith/plpgsql.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/cockroachdb/errors"
1515
)
1616

17+
var plpgsqlFlags = tree.FmtParsable | tree.FmtPLpgSQLParen
18+
1719
func (s *Smither) makeRoutineBodyPLpgSQL(
1820
params tree.ParamTypes, rTyp *types.T, vol tree.RoutineVolatility,
1921
) string {
@@ -25,7 +27,7 @@ func (s *Smither) makeRoutineBodyPLpgSQL(
2527
// errors.
2628
block := s.makePLpgSQLBlock(scope)
2729
block.Body = append(block.Body, s.makePLpgSQLReturn(scope))
28-
return "\n" + tree.AsStringWithFlags(s.makePLpgSQLBlock(scope), tree.FmtParsable)
30+
return "\n" + tree.AsStringWithFlags(s.makePLpgSQLBlock(scope), plpgsqlFlags, tree.FmtInPLpgSQL(true /* inPLpgSQL */))
2931
}
3032

3133
func (s *Smither) makePLpgSQLBlock(scope plpgsqlBlockScope) *ast.Block {

pkg/internal/sqlsmith/setup_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ package sqlsmith_test
88
import (
99
"context"
1010
"flag"
11-
"fmt"
12-
"strings"
1311
"testing"
1412

1513
"github.com/cockroachdb/cockroach/pkg/base"
@@ -92,9 +90,7 @@ func TestGenerateParse(t *testing.T) {
9290
t.Fatalf("unknown setting %s", settingName)
9391
}
9492
settings := setting(rnd)
95-
t.Log("setting:", settingName, settings.Options)
9693
setupSQL := setup(rnd)
97-
t.Log(strings.Join(setupSQL, "\n"))
9894
for _, stmt := range setupSQL {
9995
db.Exec(t, stmt)
10096
}
@@ -119,7 +115,6 @@ func TestGenerateParse(t *testing.T) {
119115
if err != nil {
120116
t.Fatal(err)
121117
}
122-
fmt.Print("STMT: ", i, "\n", stmt, ";\n\n")
123118
if *flagExec {
124119
_, err = conn.ExecContext(ctx, `SET statement_timeout = '9s'`)
125120
if err != nil {
@@ -129,7 +124,7 @@ func TestGenerateParse(t *testing.T) {
129124
es := err.Error()
130125
if !seen[es] {
131126
seen[es] = true
132-
fmt.Printf("ERR (%d): %v\n", i, err)
127+
t.Logf("ERR (%d): %v\n", i, err)
133128
}
134129
}
135130
}

pkg/internal/sqlsmith/sqlsmith.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ var prettyCfg = func() tree.PrettyCfg {
192192
cfg := tree.DefaultPrettyCfg()
193193
cfg.LineWidth = 120
194194
cfg.Simplify = false
195+
cfg.FmtFlags = tree.FmtPLpgSQLParen
195196
return cfg
196197
}()
197198

@@ -213,10 +214,10 @@ func (s *Smither) Generate() string {
213214
i = 0
214215

215216
printCfg := prettyCfg
216-
fl := tree.FmtParsable
217+
fl := plpgsqlFlags
217218
if s.postgres {
218-
printCfg.FmtFlags = tree.FmtPGCatalog
219-
fl = tree.FmtPGCatalog
219+
printCfg.FmtFlags = tree.FmtPGCatalog | tree.FmtPLpgSQLParen
220+
fl = tree.FmtPGCatalog | tree.FmtPLpgSQLParen
220221
}
221222
p, err := printCfg.Pretty(stmt)
222223
if err != nil {

pkg/internal/team/TEAMS.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ cockroachdb/docs:
77
cockroachdb/docs-infra-prs: other
88
cockroachdb/sql-foundations:
99
aliases:
10-
cockroachdb/sql-syntax-prs: other
1110
cockroachdb/sqlproxy-prs: other
1211
cockroachdb/sql-api-prs: other
1312
label: T-sql-foundations

pkg/sql/logictest/testdata/logic_test/generic

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,9 @@ plan type: custom
474474
statement ok
475475
DEALLOCATE p
476476

477-
# Regression test for #155159. Do not choose a generic query plan with unbounded
478-
# cardinality when the custom plans have bounded cardinality.
477+
# Regression test for #155159 and #156690. Do not choose a generic query plan
478+
# with reads that have unbounded cardinality when the reads in the custom plan
479+
# have bounded cardinality.
479480
statement ok
480481
CREATE TABLE t155159 (
481482
id INT PRIMARY KEY,
@@ -540,3 +541,84 @@ quality of service: regular
540541

541542
statement ok
542543
DEALLOCATE p
544+
545+
statement ok
546+
PREPARE p AS
547+
SELECT * FROM (
548+
SELECT id FROM t155159 WHERE a = $1 AND b >= $2
549+
ORDER BY b, id
550+
LIMIT 250
551+
)
552+
UNION
553+
SELECT * FROM (
554+
SELECT id FROM t155159 WHERE a = $3 AND b >= $4
555+
)
556+
557+
statement ok
558+
EXECUTE p (33, 44, 55, 66)
559+
560+
statement ok
561+
EXECUTE p (33, 44, 55, 66)
562+
563+
statement ok
564+
EXECUTE p (33, 44, 55, 66)
565+
566+
statement ok
567+
EXECUTE p (33, 44, 55, 66)
568+
569+
statement ok
570+
EXECUTE p (33, 44, 55, 66)
571+
572+
query T
573+
EXPLAIN ANALYZE EXECUTE p (33, 44, 55, 66);
574+
----
575+
planning time: 10µs
576+
execution time: 100µs
577+
distribution: <hidden>
578+
vectorized: <hidden>
579+
plan type: custom
580+
maximum memory usage: <hidden>
581+
DistSQL network usage: <hidden>
582+
regions: <hidden>
583+
isolation level: serializable
584+
priority: normal
585+
quality of service: regular
586+
·
587+
• union
588+
│ sql nodes: <hidden>
589+
│ regions: <hidden>
590+
│ actual row count: 0
591+
│ execution time: 0µs
592+
│ estimated max memory allocated: 0 B
593+
594+
├── • scan
595+
│ sql nodes: <hidden>
596+
│ kv nodes: <hidden>
597+
│ regions: <hidden>
598+
│ actual row count: 0
599+
│ KV time: 0µs
600+
│ KV rows decoded: 0
601+
│ KV bytes read: 0 B
602+
│ KV gRPC calls: 0
603+
│ estimated max memory allocated: 0 B
604+
│ missing stats
605+
│ table: t155159@t155159_a_b_idx
606+
│ spans: [/33/44 - /33]
607+
│ limit: 250
608+
609+
└── • scan
610+
sql nodes: <hidden>
611+
kv nodes: <hidden>
612+
regions: <hidden>
613+
actual row count: 0
614+
KV time: 0µs
615+
KV rows decoded: 0
616+
KV bytes read: 0 B
617+
KV gRPC calls: 0
618+
estimated max memory allocated: 0 B
619+
missing stats
620+
table: t155159@t155159_a_b_idx
621+
spans: [/55/66 - /55]
622+
623+
statement ok
624+
DEALLOCATE p

pkg/sql/opt/memo/cost.go

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@ type Cost struct {
2222
// cost is compared to other costs with Less.
2323
aux struct {
2424
// fullScanCount is the number of full table or index scans in a
25-
// sub-plan, up to 255.
26-
fullScanCount uint8
27-
// unboundedCardinality is true if the operator or any of its
28-
// descendants have no guaranteed upperbound on the number of rows that
29-
// they can produce. It is similar to UnboundedCardinalityPenalty, but
30-
// different in that it is used to propagate the same information up the
31-
// tree without affecting cost comparisons.
32-
unboundedCardinality bool
25+
// sub-plan, up to 65535.
26+
fullScanCount uint16
27+
// unboundedReadCount is the number of read expressions (e.g., scans,
28+
// lookup joins, etc.) in a sub-plan that have no upper-bound
29+
// cardinality, up to 65535.
30+
unboundedReadCount uint16
3331
}
3432
}
3533

@@ -63,45 +61,28 @@ func (c Cost) Less(other Cost) bool {
6361
func (c *Cost) Add(other Cost) {
6462
c.C += other.C
6563
c.Penalties |= other.Penalties
66-
if c.aux.fullScanCount > math.MaxUint8-other.aux.fullScanCount {
67-
// Avoid overflow.
68-
c.aux.fullScanCount = math.MaxUint8
69-
} else {
70-
c.aux.fullScanCount += other.aux.fullScanCount
71-
}
72-
c.aux.unboundedCardinality = c.aux.unboundedCardinality || other.aux.unboundedCardinality
64+
c.aux.fullScanCount = addUint16(c.aux.fullScanCount, other.aux.fullScanCount)
65+
c.aux.unboundedReadCount = addUint16(c.aux.unboundedReadCount, other.aux.unboundedReadCount)
7366
}
7467

7568
// FullScanCount returns the number of full scans in the cost.
76-
func (c Cost) FullScanCount() uint8 {
69+
func (c Cost) FullScanCount() uint16 {
7770
return c.aux.fullScanCount
7871
}
7972

8073
// IncrFullScanCount increments that auxiliary full scan count within c.
8174
func (c *Cost) IncrFullScanCount() {
82-
// Avoid overflow.
83-
if c.aux.fullScanCount < math.MaxUint8 {
84-
c.aux.fullScanCount++
85-
}
75+
c.aux.fullScanCount = addUint16(c.aux.fullScanCount, 1)
8676
}
8777

88-
// HasUnboundedCardinality returns true if any expression in the tree has no
89-
// guaranteed upperbound on the number of rows that it will produce.
90-
//
91-
// NOTE: The returned value is independent of the UnboundedCardinalityPenalty
92-
// and true may be returned when the penalty is not set. It has no effect on
93-
// cost comparisons.
94-
func (c Cost) HasUnboundedCardinality() bool {
95-
return c.aux.unboundedCardinality
78+
// UnboundedReadCount returns the number of full scans in the cost.
79+
func (c Cost) UnboundedReadCount() uint16 {
80+
return c.aux.unboundedReadCount
9681
}
9782

98-
// SetUnboundedCardinality is called to indicate that an expression has no
99-
// guaranteed upperbound on the number of rows that it will produce.
100-
//
101-
// NOTE: This flag does not affect cost comparisons and is independent of the
102-
// UnboundedCardinalityPenalty.
103-
func (c *Cost) SetUnboundedCardinality() {
104-
c.aux.unboundedCardinality = true
83+
// IncrUnboundedReadCount increments that auxiliary full scan count within c.
84+
func (c *Cost) IncrUnboundedReadCount() {
85+
c.aux.unboundedReadCount = addUint16(c.aux.unboundedReadCount, 1)
10586
}
10687

10788
// Penalties is an ordered bitmask where each bit indicates a cost penalty. The
@@ -142,12 +123,11 @@ const (
142123
// <Cost> is the floating point cost value.
143124
// <Penalties> contains "H", "F", or "U" for HugeCostPenalty, FullScanPenalty,
144125
// and UnboundedCardinalityPenalty, respectively.
145-
// <aux> contains a number for full scan count and "u" for
146-
// unboundedCardinality.
126+
// <aux> contains the number of full scans and unbounded reads.
147127
//
148-
// For example, the summary "1.23:HF:5fu" indicates a cost of 1.23 with the
149-
// HugeCostPenalty and FullScanPenalty penalties, 5 full scans, and the
150-
// unboundedCardinality flag set.
128+
// For example, the summary "1.23:HF:5f6u" indicates a cost of 1.23 with the
129+
// HugeCostPenalty and FullScanPenalty penalties, 5 full scans, and 6 unbounded
130+
// reads.
151131
func (c Cost) Summary() string {
152132
var sb strings.Builder
153133
_, _ = fmt.Fprintf(&sb, "%.9g:", c.C)
@@ -161,8 +141,13 @@ func (c Cost) Summary() string {
161141
sb.WriteByte('U')
162142
}
163143
_, _ = fmt.Fprintf(&sb, ":%df", c.aux.fullScanCount)
164-
if c.aux.unboundedCardinality {
165-
sb.WriteByte('u')
166-
}
144+
_, _ = fmt.Fprintf(&sb, "%du", c.aux.unboundedReadCount)
167145
return sb.String()
168146
}
147+
148+
func addUint16(a, b uint16) uint16 {
149+
if a > math.MaxUint16-b {
150+
return math.MaxUint16
151+
}
152+
return a + b
153+
}

pkg/sql/opt/memo/cost_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ package memo
88
import "testing"
99

1010
type testAux struct {
11-
fullScanCount uint8
12-
unboundedCardinality bool
11+
fullScanCount uint16
12+
unboundedReadCount uint16
1313
}
1414

1515
func TestCostLess(t *testing.T) {
@@ -39,8 +39,8 @@ func TestCostLess(t *testing.T) {
3939
{Cost{C: 2.0}, Cost{C: 1.0, Penalties: UnboundedCardinalityPenalty}, true},
4040
{Cost{C: 1.0, Penalties: UnboundedCardinalityPenalty}, Cost{C: 2.0}, false},
4141
// Auxiliary information should not affect the comparison.
42-
{Cost{C: 1.0, aux: testAux{0, false}}, Cost{C: 1.0, aux: testAux{1, true}}, false},
43-
{Cost{C: 1.0, aux: testAux{1, true}}, Cost{C: 1.0, aux: testAux{0, false}}, false},
42+
{Cost{C: 1.0, aux: testAux{0, 0}}, Cost{C: 1.0, aux: testAux{1, 1}}, false},
43+
{Cost{C: 1.0, aux: testAux{1, 1}}, Cost{C: 1.0, aux: testAux{0, 0}}, false},
4444
}
4545
for _, tc := range testCases {
4646
if tc.left.Less(tc.right) != tc.expected {
@@ -60,8 +60,8 @@ func TestCostAdd(t *testing.T) {
6060
{Cost{C: 1.0, Penalties: FullScanPenalty}, Cost{C: 2.0}, Cost{C: 3.0, Penalties: FullScanPenalty}},
6161
{Cost{C: 1.0}, Cost{C: 2.0, Penalties: HugeCostPenalty}, Cost{C: 3.0, Penalties: HugeCostPenalty}},
6262
{Cost{C: 1.0, Penalties: UnboundedCardinalityPenalty}, Cost{C: 2.0, Penalties: HugeCostPenalty}, Cost{C: 3.0, Penalties: HugeCostPenalty | UnboundedCardinalityPenalty}},
63-
{Cost{C: 1.0, aux: testAux{1, false}}, Cost{C: 1.0, aux: testAux{2, true}}, Cost{C: 2.0, aux: testAux{3, true}}},
64-
{Cost{C: 1.0, aux: testAux{200, true}}, Cost{C: 1.0, aux: testAux{100, false}}, Cost{C: 2.0, aux: testAux{255, true}}},
63+
{Cost{C: 1.0, aux: testAux{1, 4}}, Cost{C: 1.0, aux: testAux{2, 5}}, Cost{C: 2.0, aux: testAux{3, 9}}},
64+
{Cost{C: 1.0, aux: testAux{65530, 65530}}, Cost{C: 1.0, aux: testAux{100, 100}}, Cost{C: 2.0, aux: testAux{65535, 65535}}},
6565
}
6666
for _, tc := range testCases {
6767
tc.left.Add(tc.right)
@@ -76,18 +76,18 @@ func TestCostSummary(t *testing.T) {
7676
c Cost
7777
expected string
7878
}{
79-
{Cost{C: 1.0}, "1::0f"},
80-
{Cost{C: 1.23}, "1.23::0f"},
81-
{Cost{C: 1.23456}, "1.23456::0f"},
82-
{Cost{C: 1.23, Penalties: HugeCostPenalty}, "1.23:H:0f"},
83-
{Cost{C: 1.23, Penalties: FullScanPenalty}, "1.23:F:0f"},
84-
{Cost{C: 1.23, Penalties: UnboundedCardinalityPenalty}, "1.23:U:0f"},
85-
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty | UnboundedCardinalityPenalty}, "1.23:HFU:0f"},
86-
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty | UnboundedCardinalityPenalty}, "1.23:HFU:0f"},
87-
{Cost{C: 1.23, aux: testAux{5, false}}, "1.23::5f"},
88-
{Cost{C: 1.23, aux: testAux{0, true}}, "1.23::0fu"},
89-
{Cost{C: 1.23, aux: testAux{5, true}}, "1.23::5fu"},
90-
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty, aux: testAux{5, true}}, "1.23:HF:5fu"},
79+
{Cost{C: 1.0}, "1::0f0u"},
80+
{Cost{C: 1.23}, "1.23::0f0u"},
81+
{Cost{C: 1.23456}, "1.23456::0f0u"},
82+
{Cost{C: 1.23, Penalties: HugeCostPenalty}, "1.23:H:0f0u"},
83+
{Cost{C: 1.23, Penalties: FullScanPenalty}, "1.23:F:0f0u"},
84+
{Cost{C: 1.23, Penalties: UnboundedCardinalityPenalty}, "1.23:U:0f0u"},
85+
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty | UnboundedCardinalityPenalty}, "1.23:HFU:0f0u"},
86+
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty | UnboundedCardinalityPenalty}, "1.23:HFU:0f0u"},
87+
{Cost{C: 1.23, aux: testAux{5, 0}}, "1.23::5f0u"},
88+
{Cost{C: 1.23, aux: testAux{0, 6}}, "1.23::0f6u"},
89+
{Cost{C: 1.23, aux: testAux{5, 10}}, "1.23::5f10u"},
90+
{Cost{C: 1.23, Penalties: HugeCostPenalty | FullScanPenalty, aux: testAux{5, 9}}, "1.23:HF:5f9u"},
9191
}
9292
for _, tc := range testCases {
9393
if r := tc.c.Summary(); r != tc.expected {

0 commit comments

Comments
 (0)