Skip to content

Commit e762b66

Browse files
craig[bot]ZhouXing19andyyang890
committed
151891: sqlsmith: support `EXIT` and `CONTINUE` for PLpgSQL r=ZhouXing19 a=ZhouXing19 Informs #106368 Release note: None 151944: changefeedccl: disable schema_locked in TestChangefeedTruncateOrDrop r=fqazi a=andyyang890 This patch disables `schema_locked` on the tables used in `TestChangefeedTruncateOrDrop` because of a known issue where `schema_locked` does not block `TRUNCATE`, which could cause this test to flake. Informs #151523 Informs #151273 Release note: None Co-authored-by: ZhouXing19 <[email protected]> Co-authored-by: Andy Yang <[email protected]>
3 parents beecf57 + 0d8573a + 44fdf34 commit e762b66

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

pkg/ccl/changefeedccl/changefeed_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6281,8 +6281,9 @@ func TestChangefeedTruncateOrDrop(t *testing.T) {
62816281
return err
62826282
}
62836283

6284-
sqlDB.Exec(t, `CREATE TABLE truncate (a INT PRIMARY KEY)`)
6285-
sqlDB.Exec(t, `CREATE TABLE truncate_cascade (b INT PRIMARY KEY REFERENCES truncate (a))`)
6284+
// TODO(#151941): Re-enable auto schema_locked for this test.
6285+
sqlDB.Exec(t, `CREATE TABLE truncate (a INT PRIMARY KEY) WITH (schema_locked=false)`)
6286+
sqlDB.Exec(t, `CREATE TABLE truncate_cascade (b INT PRIMARY KEY REFERENCES truncate (a)) WITH (schema_locked=false)`)
62866287
sqlDB.Exec(t,
62876288
`BEGIN; INSERT INTO truncate VALUES (1); INSERT INTO truncate_cascade VALUES (1); COMMIT`)
62886289
truncate := feed(t, f, `CREATE CHANGEFEED FOR truncate`)

pkg/internal/sqlsmith/plpgsql.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func (s *Smither) makePLpgSQLStatements(scope plpgsqlBlockScope, maxCount int) [
101101

102102
func (s *Smither) makePLpgSQLIf(scope plpgsqlBlockScope) *ast.If {
103103
const maxBranchStmts = 3
104+
scope.scopeMetas = append(scope.scopeMetas, scopeMeta{typ: ifScope})
104105
ifStmt := &ast.If{
105106
Condition: s.makePLpgSQLCond(scope),
106107
ThenBody: s.makePLpgSQLStatements(scope, maxBranchStmts),
@@ -144,6 +145,8 @@ var (
144145
{5, makePLpgSQLNull},
145146
{10, makePLpgSQLAssign},
146147
{10, makePLpgSQLExecSQL},
148+
{2, makePLpgSQLExit},
149+
{2, makePLpgSQLContinue},
147150
}
148151
)
149152

@@ -226,6 +229,30 @@ func makePLpgSQLNull(_ *Smither, _ plpgsqlBlockScope) (stmt ast.Statement, ok bo
226229
return &ast.Null{}, true
227230
}
228231

232+
func makePLpgSQLExit(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement, ok bool) {
233+
if !scope.inLoop() {
234+
// EXIT statements can only be used within loops.
235+
return nil, false
236+
}
237+
res := &ast.Exit{
238+
// TODO(#106368): optionally add a label.
239+
Condition: s.makePLpgSQLCond(scope),
240+
}
241+
return res, true
242+
}
243+
244+
func makePLpgSQLContinue(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement, ok bool) {
245+
if !scope.inLoop() {
246+
// CONTINUE statements can only be used within loops.
247+
return nil, false
248+
}
249+
res := &ast.Continue{
250+
// TODO(#106368): optionally add a label.
251+
Condition: s.makePLpgSQLCond(scope),
252+
}
253+
return res, true
254+
}
255+
229256
func makePLpgSQLForLoop(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement, ok bool) {
230257
// TODO(#105246): add support for other query and cursor FOR loops.
231258
control := ast.IntForLoopControl{
@@ -239,6 +266,7 @@ func makePLpgSQLForLoop(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement
239266
newScope := scope.makeChild(1 /* numNewVars */)
240267
loopVarName := s.makePLpgSQLVarName("loop", newScope)
241268
newScope.addVariable(string(loopVarName), types.Int, false /* constant */)
269+
newScope.scopeMetas = append(newScope.scopeMetas, scopeMeta{typ: loopScope, name: string(loopVarName)})
242270
const maxLoopStmts = 3
243271
return &ast.ForLoop{
244272
// TODO(#106368): optionally add a label.
@@ -249,14 +277,35 @@ func makePLpgSQLForLoop(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement
249277
}
250278

251279
func makePLpgSQLWhile(s *Smither, scope plpgsqlBlockScope) (stmt ast.Statement, ok bool) {
280+
newScope := scope.makeChild(1 /* numNewVars */)
281+
loopVarName := s.makePLpgSQLVarName("loop", newScope)
282+
newScope.scopeMetas = append(newScope.scopeMetas, scopeMeta{typ: loopScope, name: string(loopVarName)})
252283
const maxLoopStmts = 3
253284
return &ast.While{
254285
// TODO(#106368): optionally add a label.
255-
Condition: s.makePLpgSQLCond(scope),
256-
Body: s.makePLpgSQLStatements(scope, maxLoopStmts),
286+
Condition: s.makePLpgSQLCond(newScope),
287+
Body: s.makePLpgSQLStatements(newScope, maxLoopStmts),
257288
}, true
258289
}
259290

291+
// scopeType is a type that represents the type of scope that the current block
292+
// is nested within.
293+
type scopeType int
294+
295+
const (
296+
loopScope scopeType = iota
297+
ifScope
298+
)
299+
300+
// scopeMeta is a name-tagged scopeType. It is used to determine if certain
301+
// statements are allowed in the current scope, such as EXIT and CONTINUE,
302+
// which can only be used within loops.
303+
type scopeMeta struct {
304+
typ scopeType
305+
// // TODO(#106368): propagate `name` with the loop label.
306+
name string
307+
}
308+
260309
// plpgsqlBlockScope holds the information needed to ensure that generated
261310
// statements obey PL/pgSQL syntax and scoping rules.
262311
type plpgsqlBlockScope struct {
@@ -270,6 +319,12 @@ type plpgsqlBlockScope struct {
270319
// current block.
271320
vars []string
272321

322+
// scopeMetas tracks the nested scopes of the current block. For example,
323+
// entering a FOR loop adds a loopScope with the loop label name, while
324+
// entering an IF statement adds an ifScope without a name. Used to validate
325+
// statements like EXIT and CONTINUE, which are only allowed in loops.
326+
scopeMetas []scopeMeta
327+
273328
// refs is the list of colRefs for every variable in the current scope. It
274329
// could be rebuilt from the vars and varTypes fields, but is kept up-to-date
275330
// here for convenience.
@@ -305,9 +360,20 @@ func (s *plpgsqlBlockScope) makeChild(numNewVars int) plpgsqlBlockScope {
305360
}
306361
newScope.vars = append(newScope.vars, s.vars...)
307362
newScope.refs = append(newScope.refs, s.refs...)
363+
newScope.scopeMetas = append(newScope.scopeMetas, s.scopeMetas...)
308364
return newScope
309365
}
310366

367+
// inLoop returns true if the current scope is within a for/while loop.
368+
func (s *plpgsqlBlockScope) inLoop() bool {
369+
for _, m := range s.scopeMetas {
370+
if m.typ == loopScope {
371+
return true
372+
}
373+
}
374+
return false
375+
}
376+
311377
func (s *plpgsqlBlockScope) hasVariable(name string) bool {
312378
return s.varTypes[name] != nil
313379
}

0 commit comments

Comments
 (0)