github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/sql/sem/tree/create_routine.go (about) 1 // Copyright 2022 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tree 12 13 import ( 14 "context" 15 "strings" 16 17 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/types" 20 "github.com/cockroachdb/errors" 21 ) 22 23 // ErrConflictingRoutineOption indicates that there are conflicting or 24 // redundant function options from user input to either create or alter a 25 // function. 26 var ErrConflictingRoutineOption = pgerror.New(pgcode.Syntax, "conflicting or redundant options") 27 28 // RoutineName represent a function name in a UDF relevant statement, either 29 // DDL or DML statement. Similar to TableName, it is constructed for incoming 30 // SQL queries from an UnresolvedObjectName. 31 type RoutineName struct { 32 objName 33 } 34 35 // MakeRoutineNameFromPrefix returns a RoutineName with the given prefix and 36 // function name. 37 func MakeRoutineNameFromPrefix(prefix ObjectNamePrefix, object Name) RoutineName { 38 return RoutineName{objName{ 39 ObjectName: object, 40 ObjectNamePrefix: prefix, 41 }} 42 } 43 44 // MakeQualifiedRoutineName constructs a RoutineName with the given db and 45 // schema name as prefix. 46 func MakeQualifiedRoutineName(db string, sc string, fn string) RoutineName { 47 return MakeRoutineNameFromPrefix( 48 ObjectNamePrefix{ 49 CatalogName: Name(db), 50 ExplicitCatalog: true, 51 SchemaName: Name(sc), 52 ExplicitSchema: true, 53 }, Name(fn), 54 ) 55 } 56 57 // Format implements the NodeFormatter interface. 58 func (f *RoutineName) Format(ctx *FmtCtx) { 59 f.ObjectNamePrefix.Format(ctx) 60 if f.ExplicitSchema || ctx.alwaysFormatTablePrefix() { 61 ctx.WriteByte('.') 62 } 63 ctx.FormatNode(&f.ObjectName) 64 } 65 66 func (f *RoutineName) String() string { return AsString(f) } 67 68 // FQString renders the function name in full, not omitting the prefix 69 // schema and catalog names. Suitable for logging, etc. 70 func (f *RoutineName) FQString() string { 71 ctx := NewFmtCtx(FmtSimple) 72 ctx.FormatNode(&f.CatalogName) 73 ctx.WriteByte('.') 74 ctx.FormatNode(&f.SchemaName) 75 ctx.WriteByte('.') 76 ctx.FormatNode(&f.ObjectName) 77 return ctx.CloseAndGetString() 78 } 79 80 func (f *RoutineName) objectName() {} 81 82 // CreateRoutine represents a CREATE FUNCTION or CREATE PROCEDURE statement. 83 type CreateRoutine struct { 84 IsProcedure bool 85 Replace bool 86 Name RoutineName 87 Params RoutineParams 88 ReturnType RoutineReturnType 89 Options RoutineOptions 90 RoutineBody *RoutineBody 91 // BodyStatements is not assigned during initial parsing of user input. It's 92 // assigned during opt builder for logging purpose at the moment. It stores 93 // all parsed AST nodes of body statements with all expression in original 94 // format. That is sequence names and type name in expressions are not 95 // rewritten with OIDs. 96 BodyStatements Statements 97 // BodyAnnotations is not assigned during initial parsing of user input. It's 98 // assigned by the opt builder when the optimizer parses the body statements. 99 BodyAnnotations []*Annotations 100 } 101 102 // Format implements the NodeFormatter interface. 103 func (node *CreateRoutine) Format(ctx *FmtCtx) { 104 ctx.WriteString("CREATE ") 105 if node.Replace { 106 ctx.WriteString("OR REPLACE ") 107 } 108 if node.IsProcedure { 109 ctx.WriteString("PROCEDURE ") 110 } else { 111 ctx.WriteString("FUNCTION ") 112 } 113 ctx.FormatNode(&node.Name) 114 ctx.WriteByte('(') 115 ctx.FormatNode(node.Params) 116 ctx.WriteString(")\n\t") 117 if !node.IsProcedure { 118 ctx.WriteString("RETURNS ") 119 if node.ReturnType.SetOf { 120 ctx.WriteString("SETOF ") 121 } 122 ctx.FormatTypeReference(node.ReturnType.Type) 123 ctx.WriteString("\n\t") 124 } 125 var funcBody RoutineBodyStr 126 for _, option := range node.Options { 127 switch t := option.(type) { 128 case RoutineBodyStr: 129 funcBody = t 130 continue 131 case RoutineLeakproof, RoutineVolatility, RoutineNullInputBehavior: 132 if node.IsProcedure { 133 continue 134 } 135 } 136 ctx.FormatNode(option) 137 ctx.WriteString("\n\t") 138 } 139 140 if ctx.HasFlags(FmtMarkRedactionNode) { 141 ctx.WriteString("AS ") 142 ctx.WriteString("$$") 143 for i, stmt := range node.BodyStatements { 144 if i > 0 { 145 ctx.WriteByte(' ') 146 } 147 oldAnn := ctx.ann 148 ctx.ann = node.BodyAnnotations[i] 149 ctx.FormatNode(stmt) 150 ctx.WriteByte(';') 151 ctx.ann = oldAnn 152 } 153 ctx.WriteString("$$") 154 } else if node.RoutineBody != nil { 155 ctx.WriteString("BEGIN ATOMIC ") 156 for _, stmt := range node.RoutineBody.Stmts { 157 ctx.FormatNode(stmt) 158 ctx.WriteString("; ") 159 } 160 ctx.WriteString("END") 161 } else { 162 ctx.FormatNode(funcBody) 163 } 164 } 165 166 // RoutineBody represent a list of statements in a UDF body. 167 type RoutineBody struct { 168 // Stmts is populated during parsing. Unlike BodyStatements, we don't need 169 // to create separate Annotations for each statement. 170 Stmts Statements 171 } 172 173 // RoutineReturn represent a RETURN statement in a UDF body. 174 type RoutineReturn struct { 175 ReturnVal Expr 176 } 177 178 // Format implements the NodeFormatter interface. 179 func (node *RoutineReturn) Format(ctx *FmtCtx) { 180 ctx.WriteString("RETURN ") 181 ctx.FormatNode(node.ReturnVal) 182 } 183 184 // RoutineOptions represent a list of routine options. 185 type RoutineOptions []RoutineOption 186 187 // RoutineOption is an interface representing UDF properties. 188 type RoutineOption interface { 189 routineOption() 190 NodeFormatter 191 } 192 193 func (RoutineNullInputBehavior) routineOption() {} 194 func (RoutineVolatility) routineOption() {} 195 func (RoutineLeakproof) routineOption() {} 196 func (RoutineBodyStr) routineOption() {} 197 func (RoutineLanguage) routineOption() {} 198 199 // RoutineNullInputBehavior represent the UDF property on null parameters. 200 type RoutineNullInputBehavior int 201 202 const ( 203 // RoutineCalledOnNullInput indicates that the routine will be given the 204 // chance to execute when presented with NULL input. This is the default if 205 // no null input behavior is specified. 206 RoutineCalledOnNullInput RoutineNullInputBehavior = iota 207 // RoutineReturnsNullOnNullInput indicates that the routine will result in 208 // NULL given any NULL parameter. 209 RoutineReturnsNullOnNullInput 210 // RoutineStrict is the same as RoutineReturnsNullOnNullInput 211 RoutineStrict 212 ) 213 214 // Format implements the NodeFormatter interface. 215 func (node RoutineNullInputBehavior) Format(ctx *FmtCtx) { 216 switch node { 217 case RoutineCalledOnNullInput: 218 ctx.WriteString("CALLED ON NULL INPUT") 219 case RoutineReturnsNullOnNullInput: 220 ctx.WriteString("RETURNS NULL ON NULL INPUT") 221 case RoutineStrict: 222 ctx.WriteString("STRICT") 223 default: 224 panic(pgerror.New(pgcode.InvalidParameterValue, "Unknown routine option")) 225 } 226 } 227 228 // RoutineVolatility represent UDF volatility property. 229 type RoutineVolatility int 230 231 const ( 232 // RoutineVolatile represents volatility.Volatile. This is the default 233 // volatility if none is provided. 234 RoutineVolatile RoutineVolatility = iota 235 // RoutineImmutable represents volatility.Immutable. 236 RoutineImmutable 237 // RoutineStable represents volatility.Stable. 238 RoutineStable 239 ) 240 241 // Format implements the NodeFormatter interface. 242 func (node RoutineVolatility) Format(ctx *FmtCtx) { 243 switch node { 244 case RoutineVolatile: 245 ctx.WriteString("VOLATILE") 246 case RoutineImmutable: 247 ctx.WriteString("IMMUTABLE") 248 case RoutineStable: 249 ctx.WriteString("STABLE") 250 default: 251 panic(pgerror.New(pgcode.InvalidParameterValue, "unknown routine option")) 252 } 253 } 254 255 // RoutineLeakproof indicates whether a function is leakproof or not. The 256 // default is NOT LEAKPROOF if no leakproof option is provided. LEAKPROOF can 257 // only be used with the IMMUTABLE volatility because we currently conflated 258 // LEAKPROOF as a volatility equal to IMMUTABLE+LEAKPROOF. Postgres allows 259 // STABLE+LEAKPROOF functions. 260 type RoutineLeakproof bool 261 262 // Format implements the NodeFormatter interface. 263 func (node RoutineLeakproof) Format(ctx *FmtCtx) { 264 if !node { 265 ctx.WriteString("NOT ") 266 } 267 ctx.WriteString("LEAKPROOF") 268 } 269 270 // RoutineLanguage indicates the language of the statements in the routine body. 271 type RoutineLanguage string 272 273 const ( 274 // RoutineLangUnknown represents an unknown language. 275 RoutineLangUnknown RoutineLanguage = "unknown" 276 // RoutineLangSQL represents SQL language. 277 RoutineLangSQL RoutineLanguage = "SQL" 278 // RoutineLangPLpgSQL represents the PL/pgSQL procedural language. 279 RoutineLangPLpgSQL RoutineLanguage = "plpgsql" 280 // RoutineLangC represents the C language. 281 RoutineLangC RoutineLanguage = "C" 282 ) 283 284 // Format implements the NodeFormatter interface. 285 func (node RoutineLanguage) Format(ctx *FmtCtx) { 286 ctx.WriteString("LANGUAGE ") 287 ctx.WriteString(string(node)) 288 } 289 290 // AsRoutineLanguage converts a string to a RoutineLanguage if applicable. 291 // No error is returned if string does not represent a valid UDF language; 292 // unknown languages result in an error later. 293 func AsRoutineLanguage(lang string) (RoutineLanguage, error) { 294 switch strings.ToLower(lang) { 295 case "sql": 296 return RoutineLangSQL, nil 297 case "plpgsql": 298 return RoutineLangPLpgSQL, nil 299 case "c": 300 return RoutineLangC, nil 301 } 302 return RoutineLanguage(lang), nil 303 } 304 305 // RoutineBodyStr is a string containing all statements in a UDF body. 306 type RoutineBodyStr string 307 308 // Format implements the NodeFormatter interface. 309 func (node RoutineBodyStr) Format(ctx *FmtCtx) { 310 ctx.WriteString("AS ") 311 if ctx.flags.HasFlags(FmtTagDollarQuotes) { 312 ctx.WriteString("$funcbody$") 313 } else { 314 ctx.WriteString("$$") 315 } 316 if ctx.flags.HasFlags(FmtAnonymize) || ctx.flags.HasFlags(FmtHideConstants) { 317 ctx.WriteString("_") 318 } else { 319 ctx.WriteString(string(node)) 320 } 321 if ctx.flags.HasFlags(FmtTagDollarQuotes) { 322 ctx.WriteString("$funcbody$") 323 } else { 324 ctx.WriteString("$$") 325 } 326 } 327 328 // RoutineParams represents a list of RoutineParam. 329 type RoutineParams []RoutineParam 330 331 // Format implements the NodeFormatter interface. 332 func (node RoutineParams) Format(ctx *FmtCtx) { 333 for i := range node { 334 if i > 0 { 335 ctx.WriteString(", ") 336 } 337 ctx.FormatNode(&node[i]) 338 } 339 } 340 341 // RoutineParam represents a parameter in a UDF signature. 342 type RoutineParam struct { 343 Name Name 344 Type ResolvableTypeReference 345 Class RoutineParamClass 346 DefaultVal Expr 347 } 348 349 // Format implements the NodeFormatter interface. 350 func (node *RoutineParam) Format(ctx *FmtCtx) { 351 switch node.Class { 352 case RoutineParamIn: 353 ctx.WriteString("IN") 354 case RoutineParamOut: 355 ctx.WriteString("OUT") 356 case RoutineParamInOut: 357 ctx.WriteString("INOUT") 358 case RoutineParamVariadic: 359 ctx.WriteString("VARIADIC") 360 default: 361 panic(pgerror.New(pgcode.InvalidParameterValue, "unknown routine option")) 362 } 363 ctx.WriteString(" ") 364 if node.Name != "" { 365 ctx.FormatNode(&node.Name) 366 ctx.WriteString(" ") 367 } 368 ctx.FormatTypeReference(node.Type) 369 if node.DefaultVal != nil { 370 ctx.WriteString(" DEFAULT ") 371 ctx.FormatNode(node.DefaultVal) 372 } 373 } 374 375 // RoutineParamClass indicates what type of argument an arg is. 376 type RoutineParamClass int 377 378 const ( 379 // RoutineParamIn args can only be used as input. 380 RoutineParamIn RoutineParamClass = iota 381 // RoutineParamOut args can only be used as output. 382 RoutineParamOut 383 // RoutineParamInOut args can be used as both input and output. 384 RoutineParamInOut 385 // RoutineParamVariadic args are variadic. 386 RoutineParamVariadic 387 ) 388 389 // RoutineReturnType represent the return type of UDF. 390 type RoutineReturnType struct { 391 Type ResolvableTypeReference 392 SetOf bool 393 } 394 395 // DropRoutine represents a DROP FUNCTION or DROP PROCEDURE statement. 396 type DropRoutine struct { 397 IfExists bool 398 Procedure bool 399 Routines RoutineObjs 400 DropBehavior DropBehavior 401 } 402 403 // Format implements the NodeFormatter interface. 404 func (node *DropRoutine) Format(ctx *FmtCtx) { 405 if node.Procedure { 406 ctx.WriteString("DROP PROCEDURE ") 407 } else { 408 ctx.WriteString("DROP FUNCTION ") 409 } 410 if node.IfExists { 411 ctx.WriteString("IF EXISTS ") 412 } 413 ctx.FormatNode(node.Routines) 414 if node.DropBehavior != DropDefault { 415 ctx.WriteString(" ") 416 ctx.WriteString(node.DropBehavior.String()) 417 } 418 } 419 420 // RoutineObjs is a slice of RoutineObj. 421 type RoutineObjs []RoutineObj 422 423 // Format implements the NodeFormatter interface. 424 func (node RoutineObjs) Format(ctx *FmtCtx) { 425 for i := range node { 426 if i > 0 { 427 ctx.WriteString(", ") 428 } 429 ctx.FormatNode(&node[i]) 430 } 431 } 432 433 // RoutineObj represents a routine (function or procedure) object in DROP, 434 // GRANT, and REVOKE statements. 435 type RoutineObj struct { 436 FuncName RoutineName 437 Params RoutineParams 438 } 439 440 // Format implements the NodeFormatter interface. 441 func (node *RoutineObj) Format(ctx *FmtCtx) { 442 ctx.FormatNode(&node.FuncName) 443 if node.Params != nil { 444 ctx.WriteString("(") 445 ctx.FormatNode(node.Params) 446 ctx.WriteString(")") 447 } 448 } 449 450 // ParamTypes returns a slice of parameter types of the routine. 451 func (node RoutineObj) ParamTypes( 452 ctx context.Context, res TypeReferenceResolver, 453 ) ([]*types.T, error) { 454 // TODO(chengxiong): handle INOUT, OUT and VARIADIC argument classes when we 455 // support them. This is because only IN and INOUT arg types need to be 456 // considered to match a overload. 457 var argTypes []*types.T 458 if node.Params != nil { 459 argTypes = make([]*types.T, len(node.Params)) 460 for i, arg := range node.Params { 461 typ, err := ResolveType(ctx, arg.Type, res) 462 if err != nil { 463 return nil, err 464 } 465 argTypes[i] = typ 466 } 467 } 468 return argTypes, nil 469 } 470 471 // AlterFunctionOptions represents a ALTER FUNCTION...action statement. 472 type AlterFunctionOptions struct { 473 Function RoutineObj 474 Options RoutineOptions 475 } 476 477 // Format implements the NodeFormatter interface. 478 func (node *AlterFunctionOptions) Format(ctx *FmtCtx) { 479 ctx.WriteString("ALTER FUNCTION ") 480 ctx.FormatNode(&node.Function) 481 for _, option := range node.Options { 482 ctx.WriteString(" ") 483 ctx.FormatNode(option) 484 } 485 } 486 487 // AlterRoutineRename represents a ALTER FUNCTION...RENAME or 488 // ALTER PROCEDURE...RENAME statement. 489 type AlterRoutineRename struct { 490 Function RoutineObj 491 NewName Name 492 Procedure bool 493 } 494 495 // Format implements the NodeFormatter interface. 496 func (node *AlterRoutineRename) Format(ctx *FmtCtx) { 497 if node.Procedure { 498 ctx.WriteString("ALTER PROCEDURE ") 499 } else { 500 ctx.WriteString("ALTER FUNCTION ") 501 } 502 ctx.FormatNode(&node.Function) 503 ctx.WriteString(" RENAME TO ") 504 ctx.FormatNode(&node.NewName) 505 } 506 507 // AlterRoutineSetSchema represents a ALTER FUNCTION...SET SCHEMA or 508 // ALTER PROCEDURE...SET SCHEMA statement. 509 type AlterRoutineSetSchema struct { 510 Function RoutineObj 511 NewSchemaName Name 512 Procedure bool 513 } 514 515 // Format implements the NodeFormatter interface. 516 func (node *AlterRoutineSetSchema) Format(ctx *FmtCtx) { 517 if node.Procedure { 518 ctx.WriteString("ALTER PROCEDURE ") 519 } else { 520 ctx.WriteString("ALTER FUNCTION ") 521 } 522 ctx.FormatNode(&node.Function) 523 ctx.WriteString(" SET SCHEMA ") 524 ctx.FormatNode(&node.NewSchemaName) 525 } 526 527 // AlterRoutineSetOwner represents the ALTER FUNCTION...OWNER TO or 528 // ALTER PROCEDURE...OWNER TO statement. 529 type AlterRoutineSetOwner struct { 530 Function RoutineObj 531 NewOwner RoleSpec 532 Procedure bool 533 } 534 535 // Format implements the NodeFormatter interface. 536 func (node *AlterRoutineSetOwner) Format(ctx *FmtCtx) { 537 if node.Procedure { 538 ctx.WriteString("ALTER PROCEDURE ") 539 } else { 540 ctx.WriteString("ALTER FUNCTION ") 541 } 542 ctx.FormatNode(&node.Function) 543 ctx.WriteString(" OWNER TO ") 544 ctx.FormatNode(&node.NewOwner) 545 } 546 547 // AlterFunctionDepExtension represents the ALTER FUNCTION...DEPENDS ON statement. 548 type AlterFunctionDepExtension struct { 549 Function RoutineObj 550 Remove bool 551 Extension Name 552 } 553 554 // Format implements the NodeFormatter interface. 555 func (node *AlterFunctionDepExtension) Format(ctx *FmtCtx) { 556 ctx.WriteString("ALTER FUNCTION ") 557 ctx.FormatNode(&node.Function) 558 if node.Remove { 559 ctx.WriteString(" NO") 560 } 561 ctx.WriteString(" DEPENDS ON EXTENSION ") 562 ctx.WriteString(string(node.Extension)) 563 } 564 565 // UDFDisallowanceVisitor is used to determine if a type checked expression 566 // contains any UDF function sub-expression. It's needed only temporarily to 567 // disallow any usage of UDF from relation objects. 568 type UDFDisallowanceVisitor struct { 569 FoundUDF bool 570 } 571 572 // VisitPre implements the Visitor interface. 573 func (v *UDFDisallowanceVisitor) VisitPre(expr Expr) (recurse bool, newExpr Expr) { 574 if funcExpr, ok := expr.(*FuncExpr); ok && funcExpr.ResolvedOverload().HasSQLBody() { 575 v.FoundUDF = true 576 return false, expr 577 } 578 return true, expr 579 } 580 581 // VisitPost implements the Visitor interface. 582 func (v *UDFDisallowanceVisitor) VisitPost(expr Expr) (newNode Expr) { 583 return expr 584 } 585 586 // SchemaExprContext indicates in which schema change context an expression is being 587 // used in. For example, DEFAULT VALUE of a column, CHECK CONSTRAINT's 588 // expression, etc. 589 type SchemaExprContext string 590 591 const ( 592 AlterColumnTypeUsingExpr SchemaExprContext = "ALTER COLUMN TYPE USING EXPRESSION" 593 StoredComputedColumnExpr SchemaExprContext = "STORED COMPUTED COLUMN" 594 VirtualComputedColumnExpr SchemaExprContext = "VIRTUAL COMPUTED COLUMN" 595 ColumnOnUpdateExpr SchemaExprContext = "ON UPDATE" 596 ColumnDefaultExprInAddColumn SchemaExprContext = "DEFAULT (in ADD COLUMN)" 597 ColumnDefaultExprInNewTable SchemaExprContext = "DEFAULT (in CREATE TABLE)" 598 ColumnDefaultExprInNewView SchemaExprContext = "DEFAULT (in CREATE VIEW)" 599 ColumnDefaultExprInSetDefault SchemaExprContext = "DEFAULT (in SET DEFAULT)" 600 CheckConstraintExpr SchemaExprContext = "CHECK" 601 UniqueWithoutIndexPredicateExpr SchemaExprContext = "UNIQUE WITHOUT INDEX PREDICATE" 602 IndexPredicateExpr SchemaExprContext = "INDEX PREDICATE" 603 ExpressionIndexElementExpr SchemaExprContext = "EXPRESSION INDEX ELEMENT" 604 TTLExpirationExpr SchemaExprContext = "TTL EXPIRATION EXPRESSION" 605 TTLDefaultExpr SchemaExprContext = "TTL DEFAULT" 606 TTLUpdateExpr SchemaExprContext = "TTL UPDATE" 607 ) 608 609 func ComputedColumnExprContext(isVirtual bool) SchemaExprContext { 610 if isVirtual { 611 return VirtualComputedColumnExpr 612 } 613 return StoredComputedColumnExpr 614 } 615 616 // ValidateRoutineOptions checks whether there are conflicting or redundant 617 // routine options in the given slice. 618 func ValidateRoutineOptions(options RoutineOptions) error { 619 var hasLang, hasBody, hasLeakProof, hasVolatility, hasNullInputBehavior bool 620 conflictingErr := func(opt RoutineOption) error { 621 return errors.Wrapf(ErrConflictingRoutineOption, "%s", AsString(opt)) 622 } 623 for _, option := range options { 624 switch option.(type) { 625 case RoutineLanguage: 626 if hasLang { 627 return conflictingErr(option) 628 } 629 hasLang = true 630 case RoutineBodyStr: 631 if hasBody { 632 return conflictingErr(option) 633 } 634 hasBody = true 635 case RoutineLeakproof: 636 if hasLeakProof { 637 return conflictingErr(option) 638 } 639 hasLeakProof = true 640 case RoutineVolatility: 641 if hasVolatility { 642 return conflictingErr(option) 643 } 644 hasVolatility = true 645 case RoutineNullInputBehavior: 646 if hasNullInputBehavior { 647 return conflictingErr(option) 648 } 649 hasNullInputBehavior = true 650 default: 651 return pgerror.Newf(pgcode.InvalidParameterValue, "unknown function option: ", AsString(option)) 652 } 653 } 654 655 return nil 656 } 657 658 // GetRoutineVolatility tries to find a function volatility from the given list of 659 // function options. If there is no volatility found, RoutineVolatile is 660 // returned as the default. 661 func GetRoutineVolatility(options RoutineOptions) RoutineVolatility { 662 for _, option := range options { 663 switch t := option.(type) { 664 case RoutineVolatility: 665 return t 666 } 667 } 668 return RoutineVolatile 669 }