@@ -10,6 +10,7 @@ import (
1010
1111 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
1212 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
13+ "github.com/cockroachdb/cockroach/pkg/util/pretty"
1314 "github.com/cockroachdb/errors"
1415)
1516
@@ -80,6 +81,10 @@ type CreateRoutine struct {
8081 Params RoutineParams
8182 ReturnType * RoutineReturnType
8283 Options RoutineOptions
84+ // RoutineBody is set during parsing for routines that use SQL-standard
85+ // inline body syntax: either BEGIN ATOMIC ... END or a bare RETURN expr
86+ // (as opposed to the dollar-quoted AS $$ ... $$ syntax). Currently
87+ // unimplemented in the optimizer.
8388 RoutineBody * RoutineBody
8489 // BodyStatements is not assigned during initial parsing of user input. It's
8590 // assigned during opt builder for logging purpose at the moment. It stores
@@ -165,6 +170,114 @@ func (node *CreateRoutine) Format(ctx *FmtCtx) {
165170 }
166171}
167172
173+ // Doc implements the Docer interface.
174+ func (node * CreateRoutine ) Doc (p * PrettyCfg ) pretty.Doc {
175+ header := pretty .Keyword ("CREATE" )
176+ if node .Replace {
177+ header = pretty .ConcatSpace (header , pretty .Keyword ("OR REPLACE" ))
178+ }
179+ if node .IsProcedure {
180+ header = pretty .ConcatSpace (header , pretty .Keyword ("PROCEDURE" ))
181+ } else {
182+ header = pretty .ConcatSpace (header , pretty .Keyword ("FUNCTION" ))
183+ }
184+ header = pretty .ConcatSpace (header , p .Doc (& node .Name ))
185+ paramDocs := make ([]pretty.Doc , len (node .Params ))
186+ for i := range node .Params {
187+ paramDocs [i ] = p .Doc (& node .Params [i ])
188+ }
189+ header = pretty .Concat (header , p .bracket ("(" , p .commaSeparated (paramDocs ... ), ")" ))
190+
191+ var clauses []pretty.Doc
192+ if ! node .IsProcedure && node .ReturnType != nil {
193+ ret := pretty .Keyword ("RETURNS" )
194+ if node .ReturnType .SetOf {
195+ ret = pretty .ConcatSpace (ret , pretty .Keyword ("SETOF" ))
196+ }
197+ ret = pretty .ConcatSpace (ret , p .FormatType (node .ReturnType .Type ))
198+ clauses = append (clauses , ret )
199+ }
200+
201+ // Extract RoutineBodyStr from options; it is formatted separately as the body.
202+ var isPLpgSQL bool
203+ var funcBody RoutineBodyStr
204+ for _ , option := range node .Options {
205+ switch t := option .(type ) {
206+ case RoutineBodyStr :
207+ funcBody = t
208+ continue
209+ case RoutineLeakproof , RoutineVolatility , RoutineNullInputBehavior :
210+ if node .IsProcedure {
211+ continue
212+ }
213+ case RoutineLanguage :
214+ isPLpgSQL = t == RoutineLangPLpgSQL
215+ }
216+ clauses = append (clauses , p .Doc (option ))
217+ }
218+
219+ var bodyDoc pretty.Doc
220+ if node .RoutineBody != nil {
221+ stmtDocs := make ([]pretty.Doc , len (node .RoutineBody .Stmts ))
222+ for i , stmt := range node .RoutineBody .Stmts {
223+ d := p .Doc (stmt )
224+ // DoBlock.Doc already includes a trailing semicolon.
225+ if _ , ok := stmt .(* DoBlock ); ! ok {
226+ d = pretty .Concat (d , pretty .Text (";" ))
227+ }
228+ stmtDocs [i ] = d
229+ }
230+ bodyDoc = pretty .Fold (pretty .Concat ,
231+ pretty .Keyword ("BEGIN ATOMIC" ),
232+ pretty .NestT (pretty .Concat (pretty .HardLine , pretty .Stack (stmtDocs ... ))),
233+ pretty .HardLine ,
234+ pretty .Keyword ("END" ))
235+ } else if ! isPLpgSQL && ParseSQLForDoc != nil {
236+ if stmts := ParseSQLForDoc (string (funcBody )); len (stmts ) > 0 {
237+ stmtDocs := make ([]pretty.Doc , len (stmts ))
238+ for i , stmt := range stmts {
239+ d := p .Doc (stmt )
240+ // DoBlock.Doc already includes a trailing semicolon.
241+ if _ , ok := stmt .(* DoBlock ); ! ok {
242+ d = pretty .Concat (d , pretty .Text (";" ))
243+ }
244+ stmtDocs [i ] = d
245+ }
246+ // Render the body to find a dollar-quote tag that doesn't
247+ // collide with nested dollar quotes (e.g. from DO blocks).
248+ fmtCtx := NewFmtCtx (p .FmtFlagsWithDefaults ())
249+ for _ , stmt := range stmts {
250+ fmtCtx .FormatNode (stmt )
251+ }
252+ bodyStr := fmtCtx .CloseAndGetString ()
253+ tag := "$" + DollarQuoteDelimiter (bodyStr ) + "$"
254+ bodyDoc = pretty .Fold (pretty .Concat ,
255+ pretty .ConcatSpace (pretty .Keyword ("AS" ), pretty .Text (tag )),
256+ pretty .NestT (pretty .Concat (pretty .HardLine , pretty .Stack (stmtDocs ... ))),
257+ pretty .HardLine ,
258+ pretty .Text (tag ))
259+ }
260+ } else if isPLpgSQL && ParsePLpgSQLForDoc != nil {
261+ if parsed := ParsePLpgSQLForDoc (string (funcBody )); parsed != nil {
262+ // Use the formatted output to pick a dollar-quote tag that
263+ // doesn't collide with nested dollar quotes (e.g. DO blocks).
264+ bodyStr := AsStringWithFlags (parsed , p .FmtFlagsWithDefaults ())
265+ tag := "$" + DollarQuoteDelimiter (bodyStr ) + "$"
266+ bodyDoc = pretty .Fold (pretty .Concat ,
267+ pretty .ConcatSpace (pretty .Keyword ("AS" ), pretty .Text (tag )),
268+ pretty .NestT (pretty .Concat (pretty .HardLine , p .Doc (parsed ))),
269+ pretty .HardLine ,
270+ pretty .Text (tag ))
271+ }
272+ }
273+ if bodyDoc == nil {
274+ bodyDoc = p .docAsString (funcBody )
275+ }
276+ clauses = append (clauses , bodyDoc )
277+
278+ return p .nestUnder (header , pretty .Stack (clauses ... ))
279+ }
280+
168281// RoutineBody represent a list of statements in a UDF body.
169282type RoutineBody struct {
170283 // Stmts is populated during parsing. Unlike BodyStatements, we don't need
0 commit comments