-
Notifications
You must be signed in to change notification settings - Fork 3.8k
/
plpgsql.go
1867 lines (1770 loc) · 70.6 KB
/
plpgsql.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package optbuilder
import (
"context"
"fmt"
"strings"
"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/cockroach/pkg/sql/pgwire/pgcode"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/sem/builtins/builtinsregistry"
"github.com/cockroachdb/cockroach/pkg/sql/sem/cast"
ast "github.com/cockroachdb/cockroach/pkg/sql/sem/plpgsqltree"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/sem/volatility"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented"
"github.com/cockroachdb/errors"
)
// plpgsqlBuilder translates a PLpgSQL AST into a series of SQL routines that
// can be optimized and executed just like a native SQL statement. This allows
// CRDB to support PLpgSQL syntax without having to implement a specialized
// interpreter, and takes advantage of existing SQL optimizations.
//
// +---------+
// | Outline |
// +---------+
//
// The main difficulty of executing PLpgSQL with the SQL execution engine lies
// in modeling the control flow. PLpgSQL supports typical control-flow
// statements like IF and WHILE, and it allows for variables to be assigned
// within these control-flow statements. After the control-flow statement exits,
// any modifications made to variables are still visible.
//
// plpgsqlBuilder handles this by constructing a new "continuation" routine for
// each branch when it reaches a control-flow statement. The continuation
// returns the (single column) result of executing the rest of the PLpgSQL
// statements from that point on. Transfer of control at a branching point is
// then handled by explicitly calling the continuation routine for that branch.
//
// Variable declarations are handled by projecting a column; variable
// assignments are handled by projecting a new column with the same name. The
// up-to-date values for each variable are passed to each invocation of a
// continuation routine.
//
// Return statements are handled by simply projecting the returned expression.
//
// For example:
//
// CREATE FUNCTION f(x INT) RETURNS INT AS $$
// DECLARE
// i INT := 0;
// BEGIN
// LOOP
// IF i >= x THEN
// EXIT;
// END IF;
// i := i + 1;
// END LOOP;
// RETURN i;
// END
// $$ LANGUAGE PLpgSQL;
//
// This function will be (logically) broken into the following routines:
//
// CREATE FUNCTION f(x INT) RETURNS INT AS $$
// -- Initialize "i", then enter the loop.
// SELECT loop(x, i) FROM (SELECT 0 AS i);
// $$ LANGUAGE SQL;
//
// CREATE FUNCTION loop(x INT, i INT) RETURNS INT AS $$
// -- Check the IF condition, then call the correct branch continuation.
// SELECT CASE WHEN i >= x
// THEN then_branch(x, i)
// ELSE else_branch(x, i) END;
// $$ LANGUAGE SQL;
//
// CREATE FUNCTION then_branch(x INT, i INT) RETURNS INT AS $$
// -- Call the continuation for the statements after the loop.
// SELECT exit(x, i);
// $$ LANGUAGE SQL;
//
// CREATE FUNCTION else_branch(x INT, i INT) RETURNS INT AS $$
// -- Increment "i" and enter the next loop iteration.
// SELECT loop(x, i) FROM (SELECT i + 1 AS i);
// $$ LANGUAGE SQL;
//
// CREATE FUNCTION exit(x INT, i INT) RETURNS INT AS $$
// -- Return "i".
// SELECT i;
// $$ LANGUAGE SQL;
//
// Note that some of these routines may be inlined in practice (e.g. exit()).
//
// +--------------+
// | Side Effects |
// +--------------+
//
// Side-effecting expressions must be executed in order as dictated by the
// control flow of the PLpgSQL statements. This is necessary in order to provide
// the imperative interface of PLpgSQL (vs the declarative interface of SQL).
// This is guaranteed by taking care to avoid duplicating, eliminating, and
// reordering volatile expressions.
//
// When possible, these guarantees are provided by executing a volatile
// expression alone in a subroutine's body statement. Routine body statements
// are always executed in order, and serve as an optimization barrier.
//
// There are cases where a volatile expression cannot be executed as its own
// body statement, and must instead be projected from a previous scope. One
// example of this is assignment - the assigned value must be able to reference
// previous values for the PLpgSQL variables, and its result must be available
// to whichever statement comes next in the control flow. Such cases are handled
// by adding explicit optimization barriers before and after projecting the
// volatile expression. This prevents optimizations that would change side
// effects, such as pushing a volatile expression into a join or union.
// See addBarrierIfVolatile for more information.
//
// +-----------------+
// | Further Reading |
// +-----------------+
//
// See the buildPLpgSQLStatements comments for details. For further reference,
// see citations: [9] - the logic here is based on the transformation outlined
// there from PLpgSQL to "administrative normal form" (mutually tail-recursive
// functions). Note that the paper details further steps beyond ANF that we do
// not follow here, although they may be good routes for optimization in the
// future.
type plpgsqlBuilder struct {
ob *Builder
// colRefs, if non-nil, tracks the set of columns referenced by scalar
// expressions.
colRefs *opt.ColSet
// returnType is the return type of the PL/pgSQL function.
returnType *types.T
// continuations is used to model the control flow of a PL/pgSQL function.
// The head of the continuations stack is used upon reaching the end of a
// statement block to call a function that models the statements that come
// next after the block. In the context of a loop, this is used to recursively
// call back into the loop body.
continuations []continuation
// exitContinuations is similar to continuations, but is used upon reaching an
// EXIT statement within a loop. It is used to resume execution with the
// statements that follow the loop.
exitContinuations []continuation
// blocks is a stack containing every block in the path from the root block to
// the current block. It is necessary to track the entire stack because
// variables from a parent block can be referenced in a child block.
blocks []plBlock
isProcedure bool
identCounter int
}
func newPLpgSQLBuilder(
ob *Builder,
routineName string,
colRefs *opt.ColSet,
params []tree.ParamType,
returnType *types.T,
isProcedure bool,
) *plpgsqlBuilder {
const initialBlocksCap = 2
b := &plpgsqlBuilder{
ob: ob,
colRefs: colRefs,
returnType: returnType,
blocks: make([]plBlock, 0, initialBlocksCap),
isProcedure: isProcedure,
}
// Build the initial block for the routine parameters, which are considered
// PL/pgSQL variables.
b.pushBlock(plBlock{
label: routineName,
vars: make([]ast.Variable, 0, len(params)),
varTypes: make(map[ast.Variable]*types.T),
})
for _, param := range params {
b.addVariable(ast.Variable(param.Name), param.Typ)
}
return b
}
// plBlock encapsulates the local state of a PL/pgSQL block, including cursor
// and variable declarations, as well the exception handler and label.
type plBlock struct {
// label is the label provided for the block, if any. It can be used when
// resolving a PL/pgSQL variable.
label string
// vars is an ordered list of variables declared in a PL/pgSQL block.
vars []ast.Variable
// varTypes maps from the name of each variable in the scope to its type.
varTypes map[ast.Variable]*types.T
// constants tracks the variables that were declared as constant.
constants map[ast.Variable]struct{}
// cursors is the set of cursor declarations for a PL/pgSQL block. It is set
// for bound cursor declarations, which allow a query to be associated with a
// cursor before it is opened.
cursors map[ast.Variable]ast.CursorDeclaration
// hasExceptionHandler tracks whether this block has an exception handler.
hasExceptionHandler bool
// state is shared for all sub-routines that make up a PLpgSQL block,
// including the implicit block that surrounds the body statements. It is used
// for exception handling and cursor declarations. Note that the state is not
// shared between parent and child or sibling blocks - it is unique within a
// given block.
state *tree.BlockState
}
// buildRootBlock builds a PL/pgSQL routine starting with the root block.
func (b *plpgsqlBuilder) buildRootBlock(astBlock *ast.Block, s *scope) *scope {
// Push the scope, since otherwise the routine parameters could be considered
// pass-through columns when they are really outer columns.
s = s.push()
if b.isProcedure {
var tc transactionControlVisitor
ast.Walk(&tc, astBlock)
if tc.foundTxnControlStatement {
// Disable stable folding, since different parts of the routine can be run
// in different transactions.
b.ob.factory.FoldingControl().TemporarilyDisallowStableFolds(func() {
s = b.buildBlock(astBlock, s)
})
return s
}
}
return b.buildBlock(astBlock, s.push())
}
// buildBlock constructs an expression that returns the result of executing a
// PL/pgSQL block, including variable declarations and exception handlers.
//
// buildBlock should not be called externally; use buildRootBlock instead.
func (b *plpgsqlBuilder) buildBlock(astBlock *ast.Block, s *scope) *scope {
if len(b.blocks) == 0 {
// There should always be a root block for the routine parameters.
panic(errors.AssertionFailedf("expected at least one PLpgSQL block"))
}
if astBlock.Label != "" {
panic(blockLabelErr)
}
if b.block().hasExceptionHandler {
// The parent block has an exception handler. Exception handlers and nested
// blocks are not yet compatible.
panic(nestedBlockExceptionErr)
}
b.ensureScopeHasExpr(s)
block := b.pushBlock(plBlock{
label: astBlock.Label,
vars: make([]ast.Variable, 0, len(astBlock.Decls)),
varTypes: make(map[ast.Variable]*types.T),
constants: make(map[ast.Variable]struct{}),
cursors: make(map[ast.Variable]ast.CursorDeclaration),
})
defer b.popBlock()
// First, handle the variable declarations.
for i := range astBlock.Decls {
switch dec := astBlock.Decls[i].(type) {
case *ast.Declaration:
if dec.NotNull {
panic(notNullVarErr)
}
if dec.Collate != "" {
panic(collatedVarErr)
}
typ, err := tree.ResolveType(b.ob.ctx, dec.Typ, b.ob.semaCtx.TypeResolver)
if err != nil {
panic(err)
}
if types.IsRecordType(typ) {
panic(recordVarErr)
}
b.addVariable(dec.Var, typ)
if dec.Expr != nil {
// Some variable declarations initialize the variable.
s = b.addPLpgSQLAssign(s, dec.Var, dec.Expr)
} else {
// Uninitialized variables are null.
s = b.addPLpgSQLAssign(s, dec.Var, &tree.CastExpr{Expr: tree.DNull, Type: typ})
}
if dec.Constant {
// Add to the constants map after initializing the variable, since
// constant variables only prevent assignment, not initialization.
block.constants[dec.Var] = struct{}{}
}
case *ast.CursorDeclaration:
// Declaration of a bound cursor declares a variable of type refcursor.
b.addVariable(dec.Name, types.RefCursor)
s = b.addPLpgSQLAssign(s, dec.Name, &tree.CastExpr{Expr: tree.DNull, Type: types.RefCursor})
block.cursors[dec.Name] = *dec
}
}
// Perform type-checking for RECORD-returning routines. This happens after
// building the variable declarations, so that expressions that reference
// those variables can be type-checked.
if types.IsRecordType(b.returnType) {
// Infer the concrete type by examining the RETURN statements. This has to
// happen after building the declaration block because RETURN statements can
// reference declared variables.
recordVisitor := newRecordTypeVisitor(b.ob.ctx, b.ob.semaCtx, s, astBlock)
ast.Walk(recordVisitor, astBlock)
b.returnType = recordVisitor.typ
}
// Build the exception handler. This has to happen after building the variable
// declarations, since the exception handler can reference the block's vars.
if exceptions := b.buildExceptions(astBlock); exceptions != nil {
// There is an implicit block around the body statements, with an optional
// exception handler. Note that the variable declarations are not in block
// scope, and exceptions thrown during variable declaration are not caught.
//
// The routine is volatile to prevent inlining. Only the block and
// variable-assignment routines need to be volatile; see the buildExceptions
// comment for details.
block.state = &tree.BlockState{}
block.hasExceptionHandler = true
blockCon := b.makeContinuation("exception_block")
blockCon.def.ExceptionBlock = exceptions
blockCon.def.Volatility = volatility.Volatile
b.appendPlpgSQLStmts(&blockCon, astBlock.Body)
return b.callContinuation(&blockCon, s)
}
// Finally, build the body statements for the block.
return b.buildPLpgSQLStatements(astBlock.Body, s)
}
// buildPLpgSQLStatements performs the majority of the work building a PL/pgSQL
// function definition into a form that can be handled by the SQL execution
// engine. It models control flow statements by defining (possibly recursive)
// functions that model returning control after a statement block has finished
// executing. See the comments within for further detail.
//
// buildPLpgSQLStatements returns nil if one or more branches in the given
// statements do not eventually terminate with a RETURN statement.
func (b *plpgsqlBuilder) buildPLpgSQLStatements(stmts []ast.Statement, s *scope) *scope {
b.ensureScopeHasExpr(s)
for i, stmt := range stmts {
switch t := stmt.(type) {
case *ast.Block:
// For a nested block, push a continuation with the remaining statements
// before calling recursively into buildBlock. The continuation will be
// called when the control flow within the nested block terminates.
blockCon := b.makeContinuation("nested_block")
b.appendPlpgSQLStmts(&blockCon, stmts[i+1:])
b.pushContinuation(blockCon)
return b.buildBlock(t, s)
case *ast.Return:
// RETURN is handled by projecting a single column with the expression
// that is being returned.
returnScalar := b.buildPLpgSQLExpr(t.Expr, b.returnType, s)
b.addBarrierIfVolatile(s, returnScalar)
returnColName := scopeColName("").WithMetadataName(b.makeIdentifier("stmt_return"))
returnScope := s.push()
b.ob.synthesizeColumn(returnScope, returnColName, b.returnType, nil /* expr */, returnScalar)
b.ob.constructProjectForScope(s, returnScope)
return returnScope
case *ast.Assignment:
// Assignment (:=) is handled by projecting a new column with the same
// name as the variable being assigned.
s = b.addPLpgSQLAssign(s, t.Var, t.Value)
if b.hasExceptionHandler() {
// If exception handling is required, we have to start a new
// continuation after each variable assignment. This ensures that in the
// event of an error, the arguments of the currently executing routine
// will be the correct values for the variables, and can be passed to
// the exception handler routines. Set the volatility to Volatile in
// order to ensure that the routine is not inlined. See the
// handleException comment for details on why this is necessary.
catchCon := b.makeContinuation("assign_exception_block")
catchCon.def.Volatility = volatility.Volatile
b.appendPlpgSQLStmts(&catchCon, stmts[i+1:])
return b.callContinuation(&catchCon, s)
}
case *ast.If:
// IF statement control flow is handled by calling a "continuation"
// function in each branch that executes all the statements that logically
// follow the IF statement block.
//
// Create a function that models executing the statements that follow the
// IF statement. If the IF statement is the last statement in its own
// block, a statement from an ancestor block will be used.
// Example:
// IF (...) THEN ... END IF;
// RETURN (...); <-- This is used to build the continuation function.
con := b.makeContinuation("stmt_if")
b.appendPlpgSQLStmts(&con, stmts[i+1:])
b.pushContinuation(con)
// Build each branch of the IF statement, calling the continuation
// function at the end of construction in order to resume execution after
// the IF block.
thenScope := b.buildPLpgSQLStatements(t.ThenBody, s.push())
elsifScopes := make([]*scope, len(t.ElseIfList))
for j := range t.ElseIfList {
elsifScopes[j] = b.buildPLpgSQLStatements(t.ElseIfList[j].Stmts, s.push())
}
// Note that if the ELSE body is empty, elseExpr will be equivalent to
// executing the statements following the IF statement (it will be a call
// to the continuation that was built above).
elseScope := b.buildPLpgSQLStatements(t.ElseBody, s.push())
b.popContinuation()
// If one of the branches does not terminate, return nil to indicate a
// non-terminal branch.
if thenScope == nil || elseScope == nil {
return nil
}
for j := range elsifScopes {
if elsifScopes[j] == nil {
return nil
}
}
// Build a scalar CASE statement that conditionally executes each branch
// of the IF statement as a subquery.
cond := b.buildPLpgSQLExpr(t.Condition, types.Bool, s)
thenScalar := b.ob.factory.ConstructSubquery(thenScope.expr, &memo.SubqueryPrivate{})
whens := make(memo.ScalarListExpr, 0, len(t.ElseIfList)+1)
whens = append(whens, b.ob.factory.ConstructWhen(cond, thenScalar))
for j := range t.ElseIfList {
elsifCond := b.buildPLpgSQLExpr(t.ElseIfList[j].Condition, types.Bool, s)
elsifScalar := b.ob.factory.ConstructSubquery(elsifScopes[j].expr, &memo.SubqueryPrivate{})
whens = append(whens, b.ob.factory.ConstructWhen(elsifCond, elsifScalar))
}
elseScalar := b.ob.factory.ConstructSubquery(elseScope.expr, &memo.SubqueryPrivate{})
scalar := b.ob.factory.ConstructCase(memo.TrueSingleton, whens, elseScalar)
// Return a single column that projects the result of the CASE statement.
returnColName := scopeColName("").WithMetadataName(b.makeIdentifier("stmt_if"))
returnScope := s.push()
scalar = b.coerceType(scalar, b.returnType)
b.addBarrierIfVolatile(s, scalar)
b.ob.synthesizeColumn(returnScope, returnColName, b.returnType, nil /* expr */, scalar)
b.ob.constructProjectForScope(s, returnScope)
return returnScope
case *ast.Loop:
if t.Label != "" {
panic(loopLabelErr)
}
// LOOP control flow is handled similarly to IF statements, but two
// continuation functions are used - one that executes the loop body, and
// one that executes the statements following the LOOP statement. These
// are used while building the loop body, which means that its definition
// is recursive.
//
// Upon reaching the end of the loop body statements or a CONTINUE
// statement, the loop body function is called. Upon reaching an EXIT
// statement, the exit continuation is called to model returning control
// flow to the statements outside the loop.
exitCon := b.makeContinuation("loop_exit")
b.appendPlpgSQLStmts(&exitCon, stmts[i+1:])
b.pushExitContinuation(exitCon)
loopContinuation := b.makeRecursiveContinuation("stmt_loop")
b.pushContinuation(loopContinuation)
b.appendPlpgSQLStmts(&loopContinuation, t.Body)
b.popContinuation()
b.popExitContinuation()
return b.callContinuation(&loopContinuation, s)
case *ast.While:
// A WHILE LOOP is syntactic sugar for a LOOP with a conditional
// EXIT, so it is handled by a simple rewrite:
//
// WHILE [cond] LOOP
// [body];
// END LOOP;
// =>
// LOOP
// IF [cond] THEN
// [body];
// ELSE
// EXIT;
// END IF;
// END LOOP;
//
loop := &ast.Loop{
Label: t.Label,
Body: []ast.Statement{&ast.If{
Condition: t.Condition,
ThenBody: t.Body,
ElseBody: []ast.Statement{&ast.Exit{}},
}},
}
newStmts := make([]ast.Statement, 0, len(stmts))
newStmts = append(newStmts, loop)
newStmts = append(newStmts, stmts[i+1:]...)
return b.buildPLpgSQLStatements(newStmts, s)
case *ast.Exit:
if t.Label != "" {
panic(exitLabelErr)
}
if t.Condition != nil {
panic(exitCondErr)
}
// EXIT statements are handled by calling the function that executes the
// statements after a loop. Errors if used outside a loop.
if con := b.getExitContinuation(); con != nil {
return b.callContinuation(con, s)
} else {
panic(exitOutsideLoopErr)
}
case *ast.Continue:
if t.Label != "" {
panic(continueLabelErr)
}
if t.Condition != nil {
panic(continueCondErr)
}
// CONTINUE statements are handled by calling the function that executes
// the loop body. Errors if used outside a loop.
if con := b.getLoopContinuation(); con != nil {
return b.callContinuation(con, s)
} else {
panic(continueOutsideLoopErr)
}
case *ast.Raise:
// RAISE statements allow the PLpgSQL function to send an error or a
// notice to the client. We handle these side effects by building them
// into a separate body statement that is only executed for its side
// effects. The remaining PLpgSQL statements then become the last body
// statement, which returns the actual result of evaluation.
//
// The synchronous notice sending behavior is implemented in the
// crdb_internal.plpgsql_raise builtin function.
con := b.makeContinuation("_stmt_raise")
con.def.Volatility = volatility.Volatile
b.appendBodyStmt(&con, b.buildPLpgSQLRaise(con.s, b.getRaiseArgs(con.s, t)))
b.appendPlpgSQLStmts(&con, stmts[i+1:])
return b.callContinuation(&con, s)
case *ast.Execute:
if t.Strict {
panic(strictIntoErr)
}
if len(t.Target) > 1 {
seenTargets := make(map[ast.Variable]struct{})
for _, name := range t.Target {
if _, ok := seenTargets[name]; ok {
panic(dupIntoErr)
}
seenTargets[name] = struct{}{}
}
}
// Create a new continuation routine to handle executing a SQL statement.
execCon := b.makeContinuation("_stmt_exec")
stmtScope := b.ob.buildStmtAtRootWithScope(t.SqlStmt, nil /* desiredTypes */, execCon.s)
if t.Target == nil {
// When there is not INTO target, build the SQL statement into a body
// statement that is only executed for its side effects.
b.appendBodyStmt(&execCon, stmtScope)
b.appendPlpgSQLStmts(&execCon, stmts[i+1:])
return b.callContinuation(&execCon, s)
}
// This statement has an INTO target. Unlike the above case, we need the
// result of executing the SQL statement, since its result is assigned to
// the target variables. We handle this using the following steps:
// 1. Build the PLpgSQL statements following this one into a
// continuation routine.
// 2. Build the INTO statement into a continuation routine that calls
// the continuation from Step 1 using its output as parameters.
// 3. Call the INTO continuation from the parent scope.
//
// Step 1: build a continuation for the remaining PLpgSQL statements.
retCon := b.makeContinuation("_stmt_exec_ret")
b.appendPlpgSQLStmts(&retCon, stmts[i+1:])
// Ensure that the SQL statement returns at most one row.
stmtScope.expr = b.ob.factory.ConstructLimit(
stmtScope.expr,
b.ob.factory.ConstructConst(tree.NewDInt(tree.DInt(1)), types.Int),
stmtScope.makeOrderingChoice(),
)
// Ensure that the SQL statement returns at least one row. The RIGHT join
// ensures that when the SQL statement returns no rows, it is extended
// with a single row of NULL values.
stmtScope.expr = b.ob.factory.ConstructRightJoin(
stmtScope.expr,
b.ob.factory.ConstructNoColsRow(),
nil, /* on */
memo.EmptyJoinPrivate,
)
// Step 2: build the INTO statement into a continuation routine that calls
// the previously built continuation.
intoScope := b.buildInto(stmtScope, t.Target)
intoScope = b.callContinuation(&retCon, intoScope)
// Step 3: call the INTO continuation from the parent scope.
b.appendBodyStmt(&execCon, intoScope)
return b.callContinuation(&execCon, s)
case *ast.Open:
// OPEN statements are used to create a CURSOR for the current session.
// This is handled by calling the plpgsql_open_cursor internal builtin
// function in a separate body statement that returns no results, similar
// to the RAISE implementation.
if t.Scroll == tree.Scroll {
panic(scrollableCursorErr)
}
openCon := b.makeContinuation("_stmt_open")
openCon.def.Volatility = volatility.Volatile
_, source, _, err := openCon.s.FindSourceProvidingColumn(b.ob.ctx, t.CurVar)
if err != nil {
if pgerror.GetPGCode(err) == pgcode.UndefinedColumn {
panic(pgerror.Newf(pgcode.Syntax, "\"%s\" is not a known variable", t.CurVar))
}
panic(err)
}
if !source.(*scopeColumn).typ.Identical(types.RefCursor) {
panic(pgerror.Newf(pgcode.DatatypeMismatch,
"variable \"%s\" must be of type cursor or refcursor", t.CurVar,
))
}
// Initialize the routine with the information needed to pipe the first
// body statement into a cursor.
query := b.resolveOpenQuery(t)
fmtCtx := b.ob.evalCtx.FmtCtx(tree.FmtSimple)
fmtCtx.FormatNode(query)
openCon.def.CursorDeclaration = &tree.RoutineOpenCursor{
NameArgIdx: source.(*scopeColumn).getParamOrd(),
Scroll: t.Scroll,
CursorSQL: fmtCtx.CloseAndGetString(),
}
openScope := b.ob.buildStmtAtRootWithScope(query, nil /* desiredTypes */, openCon.s)
if openScope.expr.Relational().CanMutate {
// Cursors with mutations are invalid.
panic(cursorMutationErr)
}
b.appendBodyStmt(&openCon, openScope)
b.appendPlpgSQLStmts(&openCon, stmts[i+1:])
// Build a statement to generate a unique name for the cursor if one
// was not supplied. Add this to its own volatile routine to ensure that
// the name generation isn't reordered with other operations. Use the
// resulting projected column as input to the OPEN continuation.
nameCon := b.makeContinuation("_gen_cursor_name")
nameCon.def.Volatility = volatility.Volatile
nameScope := b.buildCursorNameGen(&nameCon, t.CurVar)
b.appendBodyStmt(&nameCon, b.callContinuation(&openCon, nameScope))
return b.callContinuation(&nameCon, s)
case *ast.Close:
// CLOSE statements close the cursor with the name supplied by a PLpgSQL
// variable. The crdb_internal.plpgsql_close builtin function handles
// closing the cursor. Build a volatile (non-inlinable) continuation
// that calls the builtin function.
closeCon := b.makeContinuation("_stmt_close")
closeCon.def.Volatility = volatility.Volatile
const closeFnName = "crdb_internal.plpgsql_close"
props, overloads := builtinsregistry.GetBuiltinProperties(closeFnName)
if len(overloads) != 1 {
panic(errors.AssertionFailedf("expected one overload for %s", closeFnName))
}
_, source, _, err := closeCon.s.FindSourceProvidingColumn(b.ob.ctx, t.CurVar)
if err != nil {
if pgerror.GetPGCode(err) == pgcode.UndefinedColumn {
panic(pgerror.Newf(pgcode.Syntax, "\"%s\" is not a known variable", t.CurVar))
}
panic(err)
}
if !source.(*scopeColumn).typ.Identical(types.RefCursor) {
panic(pgerror.Newf(pgcode.DatatypeMismatch,
"variable \"%s\" must be of type cursor or refcursor", t.CurVar,
))
}
closeCall := b.ob.factory.ConstructFunction(
memo.ScalarListExpr{b.ob.factory.ConstructVariable(source.(*scopeColumn).id)},
&memo.FunctionPrivate{
Name: closeFnName,
Typ: types.Int,
Properties: props,
Overload: &overloads[0],
},
)
closeColName := scopeColName("").WithMetadataName(b.makeIdentifier("stmt_close"))
closeScope := closeCon.s.push()
b.ob.synthesizeColumn(closeScope, closeColName, types.Int, nil /* expr */, closeCall)
b.ob.constructProjectForScope(closeCon.s, closeScope)
b.appendBodyStmt(&closeCon, closeScope)
b.appendPlpgSQLStmts(&closeCon, stmts[i+1:])
return b.callContinuation(&closeCon, s)
case *ast.Fetch:
// FETCH and MOVE statements are used to shift the position of a SQL
// cursor and (for FETCH statements) retrieve a row from the cursor and
// assign it to one or more PLpgSQL variables. MOVE statements have no
// result, only side effects, so they are built into a separate body
// statement. FETCH statements can mutate PLpgSQL variables, so they are
// handled similarly to SELECT ... INTO statements - see above.
//
// All cursor interactions are handled by the crdb_internal.plpgsql_fetch
// builtin function.
if !t.IsMove {
if t.Cursor.FetchType == tree.FetchAll || t.Cursor.FetchType == tree.FetchBackwardAll {
panic(fetchRowsErr)
}
}
fetchCon := b.makeContinuation("_stmt_fetch")
fetchCon.def.Volatility = volatility.Volatile
fetchScope := b.buildFetch(fetchCon.s, t)
if t.IsMove {
b.appendBodyStmt(&fetchCon, fetchScope)
b.appendPlpgSQLStmts(&fetchCon, stmts[i+1:])
return b.callContinuation(&fetchCon, s)
}
// crdb_internal.plpgsql_fetch will return a tuple with the results of the
// FETCH call. Project each element as a PLpgSQL variable. The number of
// elements returned is equal to the length of the target list
// (padded with NULLs), so we can assume each target variable has a
// corresponding element.
fetchCol := fetchScope.cols[0].id
intoScope := fetchScope.push()
for j := range t.Target {
typ := b.resolveVariableForAssign(t.Target[j])
colName := scopeColName(t.Target[j])
scalar := b.ob.factory.ConstructColumnAccess(
b.ob.factory.ConstructVariable(fetchCol),
memo.TupleOrdinal(j),
)
scalar = b.coerceType(scalar, typ)
b.ob.synthesizeColumn(intoScope, colName, typ, nil /* expr */, scalar)
}
b.ob.constructProjectForScope(fetchScope, intoScope)
// Call a continuation for the remaining PLpgSQL statements from the newly
// built statement that has updated variables. Then, call the fetch
// continuation from the parent scope.
retCon := b.makeContinuation("_stmt_exec_ret")
b.appendPlpgSQLStmts(&retCon, stmts[i+1:])
intoScope = b.callContinuation(&retCon, intoScope)
b.appendBodyStmt(&fetchCon, intoScope)
return b.callContinuation(&fetchCon, s)
case *ast.TransactionControl:
// Transaction control statements are handled by a TxnControlExpr, which
// wraps a continuation for the remaining statements in the routine.
// During execution, a TxnControlExpr directs the session to commit or
// rollback the transaction, and supplies a plan for the continuation to
// run in the new transaction.
if t.Chain {
panic(txnControlWithChainErr)
}
// NOTE: postgres doesn't make the following checks until runtime.
// TODO(#88198): check the calling context, since transaction control
// statements are only allowed through a stack of SPs and DO blocks.
if b.hasExceptionHandler() {
panic(txnControlWithExceptionErr)
}
if !b.isProcedure {
panic(txnInUDFErr)
}
name := "_stmt_commit"
txnOpType := tree.StoredProcTxnCommit
if t.Rollback {
name = "_stmt_rollback"
txnOpType = tree.StoredProcTxnRollback
}
con := b.makeContinuation(name)
con.def.Volatility = volatility.Volatile
b.appendPlpgSQLStmts(&con, stmts[i+1:])
return b.callContinuationWithTxnOp(&con, s, txnOpType)
default:
panic(unsupportedPLStmtErr)
}
}
// Call the parent continuation to execute the rest of the function.
return b.callContinuation(b.getContinuation(), s)
}
// resolveOpenQuery finds and validates the query that is bound to cursor for
// the given OPEN statement.
func (b *plpgsqlBuilder) resolveOpenQuery(open *ast.Open) tree.Statement {
// Search the blocks in reverse order to ensure that more recent declarations
// are encountered first.
var boundStmt tree.Statement
for i := len(b.blocks) - 1; i >= 0; i-- {
block := &b.blocks[i]
for name := range block.cursors {
if open.CurVar == name {
boundStmt = block.cursors[name].Query
break
}
}
}
stmt := open.Query
if stmt != nil && boundStmt != nil {
// A bound cursor cannot be opened with "OPEN FOR" syntax.
panic(errors.WithHintf(
pgerror.New(pgcode.Syntax, "syntax error at or near \"FOR\""),
"cannot specify a query during OPEN for bound cursor \"%s\"", open.CurVar,
))
}
if stmt == nil && boundStmt == nil {
// The query was not specified either during cursor declaration or in the
// open statement.
panic(errors.WithHintf(
pgerror.New(pgcode.Syntax, "expected \"FOR\" at or near \"OPEN\""),
"no query was specified for cursor \"%s\"", open.CurVar,
))
}
if stmt == nil {
// This is a bound cursor.
stmt = boundStmt
}
if _, ok := stmt.(*tree.Select); !ok {
panic(pgerror.Newf(
pgcode.InvalidCursorDefinition, "cannot open %s query as cursor", stmt.StatementTag(),
))
}
return stmt
}
// buildCursorNameGen builds a statement that generates a unique name for the
// cursor if the variable containing the name is unset. The unique name
// generation is implemented by the crdb_internal.plpgsql_gen_cursor_name
// builtin function.
func (b *plpgsqlBuilder) buildCursorNameGen(nameCon *continuation, nameVar ast.Variable) *scope {
_, source, _, _ := nameCon.s.FindSourceProvidingColumn(b.ob.ctx, nameVar)
const nameFnName = "crdb_internal.plpgsql_gen_cursor_name"
props, overloads := builtinsregistry.GetBuiltinProperties(nameFnName)
if len(overloads) != 1 {
panic(errors.AssertionFailedf("expected one overload for %s", nameFnName))
}
nameCall := b.ob.factory.ConstructFunction(
memo.ScalarListExpr{b.ob.factory.ConstructVariable(source.(*scopeColumn).id)},
&memo.FunctionPrivate{
Name: nameFnName,
Typ: types.RefCursor,
Properties: props,
Overload: &overloads[0],
},
)
nameScope := nameCon.s.push()
b.ob.synthesizeColumn(nameScope, scopeColName(nameVar), types.RefCursor, nil /* expr */, nameCall)
b.ob.constructProjectForScope(nameCon.s, nameScope)
return nameScope
}
// addPLpgSQLAssign adds a PL/pgSQL assignment to the current scope as a
// new column with the variable name that projects the assigned expression.
// If there is a column with the same name in the previous scope, it will be
// replaced. This allows the plpgsqlBuilder to model variable mutations.
func (b *plpgsqlBuilder) addPLpgSQLAssign(inScope *scope, ident ast.Variable, val ast.Expr) *scope {
typ := b.resolveVariableForAssign(ident)
assignScope := inScope.push()
for i := range inScope.cols {
col := &inScope.cols[i]
if col.name.ReferenceName() == ident {
// Allow the assignment to shadow previous values for this column.
continue
}
// If the column is not an outer column, add the column as a pass-through
// column from the previous scope.
assignScope.appendColumn(col)
}
// Project the assignment as a new column.
colName := scopeColName(ident)
scalar := b.buildPLpgSQLExpr(val, typ, inScope)
b.addBarrierIfVolatile(inScope, scalar)
b.ob.synthesizeColumn(assignScope, colName, typ, nil, scalar)
b.ob.constructProjectForScope(inScope, assignScope)
return assignScope
}
// buildInto handles the mapping from the columns of a SQL statement to the
// variables in an INTO target.
func (b *plpgsqlBuilder) buildInto(stmtScope *scope, target []ast.Variable) *scope {
var targetTypes []*types.T
var targetNames []ast.Variable
if b.targetIsRecordVar(target) {
// For a single record-type variable, the SQL statement columns are assigned
// as elements of the variable, rather than the variable itself.
targetTypes = b.resolveVariableForAssign(target[0]).TupleContents()
} else {
targetNames = target
targetTypes = make([]*types.T, len(target))
for j := range target {
targetTypes[j] = b.resolveVariableForAssign(target[j])
}
}
// For each target, project an output column that aliases the
// corresponding column from the SQL statement. Previous values for the
// variables will naturally be "overwritten" by the projection, since
// input columns are always considered before outer columns when resolving
// a column reference.
intoScope := stmtScope.push()
for j, typ := range targetTypes {
var colName scopeColumnName
if targetNames != nil {
colName = scopeColName(targetNames[j])
}
var scalar opt.ScalarExpr
if j < len(stmtScope.cols) {
scalar = b.ob.factory.ConstructVariable(stmtScope.cols[j].id)
} else {
// If there are less output columns than target variables, NULL is
// assigned to any remaining targets.
scalar = b.ob.factory.ConstructConstVal(tree.DNull, typ)
}
scalar = b.coerceType(scalar, typ)
b.ob.synthesizeColumn(intoScope, colName, typ, nil /* expr */, scalar)
}
b.ob.constructProjectForScope(stmtScope, intoScope)
if b.targetIsRecordVar(target) {
// Handle a single record-type variable (see projectRecordVar for details).
intoScope = b.projectRecordVar(intoScope, target[0])
}
return intoScope
}
// buildPLpgSQLRaise builds a call to the crdb_internal.plpgsql_raise builtin
// function, which implements the notice-sending behavior of RAISE statements.
func (b *plpgsqlBuilder) buildPLpgSQLRaise(inScope *scope, args memo.ScalarListExpr) *scope {
const raiseFnName = "crdb_internal.plpgsql_raise"
props, overloads := builtinsregistry.GetBuiltinProperties(raiseFnName)
if len(overloads) != 1 {
panic(errors.AssertionFailedf("expected one overload for %s", raiseFnName))
}
raiseCall := b.ob.factory.ConstructFunction(
args,
&memo.FunctionPrivate{
Name: raiseFnName,
Typ: types.Int,
Properties: props,
Overload: &overloads[0],
},
)
raiseColName := scopeColName("").WithMetadataName(b.makeIdentifier("stmt_raise"))
raiseScope := inScope.push()
b.ob.synthesizeColumn(raiseScope, raiseColName, types.Int, nil /* expr */, raiseCall)
b.ob.constructProjectForScope(inScope, raiseScope)
return raiseScope
}
// getRaiseArgs validates the options attached to the given PLpgSQL RAISE
// statement and returns the arguments to be used for a call to the
// crdb_internal.plpgsql_raise builtin function.
func (b *plpgsqlBuilder) getRaiseArgs(s *scope, raise *ast.Raise) memo.ScalarListExpr {
var severity, message, detail, hint, code opt.ScalarExpr
makeConstStr := func(str string) opt.ScalarExpr {
return b.ob.factory.ConstructConstVal(tree.NewDString(str), types.String)
}
// Retrieve the error/notice severity.
logLevel := strings.ToUpper(raise.LogLevel)
if logLevel == "" {
// EXCEPTION is the default log level.
logLevel = "EXCEPTION"
}
switch logLevel {
case "EXCEPTION":
// ERROR is the equivalent severity to log-level EXCEPTION.
severity = makeConstStr("ERROR")
case "LOG", "INFO", "NOTICE", "WARNING":
severity = makeConstStr(logLevel)
case "DEBUG":
// DEBUG log-level maps to severity DEBUG1.
severity = makeConstStr("DEBUG1")
default:
panic(errors.AssertionFailedf("unexpected log level %s", raise.LogLevel))
}
// Retrieve the message, if it was set with the format syntax.
if raise.Message != "" {
message = b.makeRaiseFormatMessage(s, raise.Message, raise.Params)
}
if raise.Code != "" {
if !pgcode.IsValidPGCode(raise.Code) {
panic(pgerror.Newf(pgcode.Syntax, "invalid SQLSTATE code '%s'", raise.Code))