Skip to main content

qusql_parse/
create_function.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12use crate::{
13    DataType, Expression, Identifier, SString, Span, Spanned, Statement,
14    create_option::CreateOption,
15    data_type::{DataTypeContext, parse_data_type},
16    expression::{PRIORITY_MAX, parse_expression_unreserved},
17    keywords::Keyword,
18    lexer::Token,
19    parser::{ParseError, Parser},
20    qualified_name::{QualifiedName, parse_qualified_name_unreserved},
21    statement::parse_statement,
22};
23use alloc::vec::Vec;
24
25/// Language of a function
26#[derive(Clone, Debug)]
27pub enum FunctionLanguage<'a> {
28    Sql(Span),
29    Plpgsql(Span),
30    Other(Identifier<'a>),
31}
32
33impl<'a> Spanned for FunctionLanguage<'a> {
34    fn span(&self) -> Span {
35        match &self {
36            FunctionLanguage::Sql(v) => v.span(),
37            FunctionLanguage::Plpgsql(v) => v.span(),
38            FunctionLanguage::Other(v) => v.span(),
39        }
40    }
41}
42
43/// Parallel safety level of a function
44#[derive(Clone, Debug)]
45pub enum FunctionParallel {
46    Safe(Span),
47    Unsafe(Span),
48    Restricted(Span),
49}
50
51impl Spanned for FunctionParallel {
52    fn span(&self) -> Span {
53        match self {
54            FunctionParallel::Safe(s) => s.clone(),
55            FunctionParallel::Unsafe(s) => s.clone(),
56            FunctionParallel::Restricted(s) => s.clone(),
57        }
58    }
59}
60
61/// Characteristic of a function
62#[derive(Clone, Debug)]
63pub enum FunctionCharacteristic<'a> {
64    Language(Span, FunctionLanguage<'a>),
65    Immutable(Span),
66    Stable(Span),
67    Volatile(Span),
68    Strict(Span),
69    CalledOnNullInput(Span),
70    ReturnsNullOnNullInput(Span),
71    Parallel(Span, FunctionParallel),
72    NotDeterministic(Span),
73    Deterministic(Span),
74    ContainsSql(Span),
75    NoSql(Span),
76    ReadsSqlData(Span),
77    ModifiesSqlData(Span),
78    SqlSecurityDefiner(Span),
79    SqlSecurityUser(Span),
80    Comment(SString<'a>),
81}
82
83impl<'a> Spanned for FunctionCharacteristic<'a> {
84    fn span(&self) -> Span {
85        match &self {
86            FunctionCharacteristic::Language(s, v) => s.join_span(v),
87            FunctionCharacteristic::NotDeterministic(v) => v.span(),
88            FunctionCharacteristic::Deterministic(v) => v.span(),
89            FunctionCharacteristic::ContainsSql(v) => v.span(),
90            FunctionCharacteristic::NoSql(v) => v.span(),
91            FunctionCharacteristic::ReadsSqlData(v) => v.span(),
92            FunctionCharacteristic::ModifiesSqlData(v) => v.span(),
93            FunctionCharacteristic::SqlSecurityDefiner(v) => v.span(),
94            FunctionCharacteristic::SqlSecurityUser(v) => v.span(),
95            FunctionCharacteristic::Comment(v) => v.span(),
96            FunctionCharacteristic::Immutable(v) => v.span(),
97            FunctionCharacteristic::Stable(v) => v.span(),
98            FunctionCharacteristic::Volatile(v) => v.span(),
99            FunctionCharacteristic::Strict(v) => v.span(),
100            FunctionCharacteristic::CalledOnNullInput(v) => v.span(),
101            FunctionCharacteristic::ReturnsNullOnNullInput(v) => v.span(),
102            FunctionCharacteristic::Parallel(s, v) => s.join_span(v),
103        }
104    }
105}
106
107/// Direction of a function argument
108#[derive(Clone, Debug)]
109pub enum FunctionParamDirection {
110    In(Span),
111    Out(Span),
112    InOut(Span),
113}
114
115impl Spanned for FunctionParamDirection {
116    fn span(&self) -> Span {
117        match &self {
118            FunctionParamDirection::In(v) => v.span(),
119            FunctionParamDirection::Out(v) => v.span(),
120            FunctionParamDirection::InOut(v) => v.span(),
121        }
122    }
123}
124
125/// A single function parameter
126#[derive(Clone, Debug)]
127pub struct FunctionParam<'a> {
128    /// Optional direction modifier (IN, OUT, INOUT)
129    pub direction: Option<FunctionParamDirection>,
130    /// Optional parameter name
131    pub name: Option<Identifier<'a>>,
132    /// Parameter type
133    pub type_: DataType<'a>,
134    /// Optional default value: (= or DEFAULT span, expression)
135    pub default: Option<(Span, Expression<'a>)>,
136}
137
138impl<'a> Spanned for FunctionParam<'a> {
139    fn span(&self) -> Span {
140        self.type_
141            .join_span(&self.direction)
142            .join_span(&self.name)
143            .join_span(&self.default.as_ref().map(|(s, e)| s.join_span(e)))
144    }
145}
146
147/// Body of a CREATE FUNCTION AS clause
148#[derive(Clone, Debug)]
149pub struct FunctionBody<'a> {
150    /// Span of the AS keyword
151    pub as_span: Span,
152    /// The body string(s) — typically one dollar-quoted string, or two strings for C functions
153    pub strings: Vec<SString<'a>>,
154}
155
156impl<'a> Spanned for FunctionBody<'a> {
157    fn span(&self) -> Span {
158        self.as_span.join_span(&self.strings)
159    }
160}
161
162/// Representation of Create Function Statement
163///
164/// This is not fully implemented yet
165///
166/// ```ignore
167/// # use qusql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, CreateFunction, Statement, Issues};
168/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
169/// #
170/// let sql = "DELIMITER $$
171/// CREATE FUNCTION add_func3(IN a INT, IN b INT, OUT c INT) RETURNS INT
172/// BEGIN
173///     SET c = 100;
174///     RETURN a + b;
175/// END;
176/// $$
177/// DELIMITER ;";
178/// let mut issues = Issues::new(sql);
179/// let mut stmts = parse_statements(sql, &mut issues, &options);
180///
181/// assert!(issues.is_empty());
182/// #
183/// let create: CreateFunction = match stmts.pop() {
184///     Some(Statement::CreateFunction(c)) => c,
185///     _ => panic!("We should get an create function statement")
186/// };
187///
188/// assert!(create.name.identifier.as_str() == "add_func3");
189/// println!("{:#?}", create.return_)
190/// ```
191#[derive(Clone, Debug)]
192pub struct CreateFunction<'a> {
193    /// Span of "CREATE"
194    pub create_span: Span,
195    /// Options after "CREATE"
196    pub create_options: Vec<CreateOption<'a>>,
197    /// Span of "FUNCTION"
198    pub function_span: Span,
199    /// Span of "IF NOT EXISTS" if specified
200    pub if_not_exists: Option<Span>,
201    /// Name of created function (may be schema-qualified in PostgreSQL, e.g. `schema.fn_name`)
202    pub name: QualifiedName<'a>,
203    /// Names and types of function arguments
204    pub params: Vec<FunctionParam<'a>>,
205    /// Span of "RETURNS"
206    pub returns_span: Span,
207    /// Type of return value
208    pub return_type: DataType<'a>,
209    /// Characteristics of created function
210    pub characteristics: Vec<FunctionCharacteristic<'a>>,
211    /// Optional AS body (PostgreSQL)
212    pub body: Option<FunctionBody<'a>>,
213    /// Statement computing return value
214    pub return_: Option<Statement<'a>>,
215}
216
217impl<'a> Spanned for CreateFunction<'a> {
218    fn span(&self) -> Span {
219        self.create_span
220            .join_span(&self.create_options)
221            .join_span(&self.function_span)
222            .join_span(&self.if_not_exists)
223            .join_span(&self.name)
224            .join_span(&self.params)
225            .join_span(&self.returns_span)
226            .join_span(&self.return_type)
227            .join_span(&self.characteristics)
228            .join_span(&self.body)
229            .join_span(&self.return_)
230    }
231}
232
233pub(crate) fn parse_create_function<'a>(
234    parser: &mut Parser<'a, '_>,
235    create_span: Span,
236    create_options: Vec<CreateOption<'a>>,
237) -> Result<CreateFunction<'a>, ParseError> {
238    let function_span = parser.consume_keyword(Keyword::FUNCTION)?;
239
240    let if_not_exists = if let Some(if_) = parser.skip_keyword(Keyword::IF) {
241        Some(
242            parser
243                .consume_keywords(&[Keyword::NOT, Keyword::EXISTS])?
244                .join_span(&if_),
245        )
246    } else {
247        None
248    };
249
250    let name = parse_qualified_name_unreserved(parser)?;
251    let mut params = Vec::new();
252    parser.consume_token(Token::LParen)?;
253    parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
254        loop {
255            if matches!(parser.token, Token::RParen) {
256                break;
257            }
258            let direction = match &parser.token {
259                Token::Ident(_, Keyword::IN) => {
260                    let in_ = parser.consume_keyword(Keyword::IN)?;
261                    if let Some(out) = parser.skip_keyword(Keyword::OUT) {
262                        Some(FunctionParamDirection::InOut(in_.join_span(&out)))
263                    } else {
264                        Some(FunctionParamDirection::In(in_))
265                    }
266                }
267                Token::Ident(_, Keyword::OUT) => Some(FunctionParamDirection::Out(
268                    parser.consume_keyword(Keyword::OUT)?,
269                )),
270                Token::Ident(_, Keyword::INOUT) => Some(FunctionParamDirection::InOut(
271                    parser.consume_keyword(Keyword::INOUT)?,
272                )),
273                _ => None,
274            };
275
276            let name = if parser.options.dialect.is_postgresql() {
277                // In PostgreSQL, params can be unnamed (type only).
278                // Peek at the next token to decide: if it's a boundary/separator,
279                // the current token is the type start (unnamed param).
280                let is_unnamed = matches!(
281                    parser.peek(),
282                    Token::Comma
283                        | Token::RParen
284                        | Token::LParen
285                        | Token::Eq
286                        | Token::Ident(_, Keyword::DEFAULT)
287                        | Token::LBracket
288                );
289                if is_unnamed {
290                    None
291                } else {
292                    Some(parser.consume_plain_identifier_unreserved()?)
293                }
294            } else {
295                Some(parser.consume_plain_identifier_unreserved()?)
296            };
297            let type_ = parse_data_type(parser, DataTypeContext::FunctionParam)?;
298            // Optional default value: '= expr' or 'DEFAULT expr'
299            let default = if let Some(eq_span) = parser.skip_token(Token::Eq) {
300                Some((eq_span, parse_expression_unreserved(parser, PRIORITY_MAX)?))
301            } else if let Some(default_span) = parser.skip_keyword(Keyword::DEFAULT) {
302                Some((
303                    default_span,
304                    parse_expression_unreserved(parser, PRIORITY_MAX)?,
305                ))
306            } else {
307                None
308            };
309            params.push(FunctionParam {
310                direction,
311                name,
312                type_,
313                default,
314            });
315            if parser.skip_token(Token::Comma).is_none() {
316                break;
317            }
318        }
319        Ok(())
320    })?;
321    parser.consume_token(Token::RParen)?;
322    let returns_span = parser.consume_keyword(Keyword::RETURNS)?;
323    let return_type = parse_data_type(parser, DataTypeContext::FunctionReturn)?;
324    let mut body: Option<FunctionBody<'_>> = None;
325    let mut characteristics = Vec::new();
326    loop {
327        let f = match &parser.token {
328            Token::Ident(_, Keyword::LANGUAGE) => {
329                let lg = parser.consume();
330                match &parser.token {
331                    Token::Ident(_, Keyword::SQL) => FunctionCharacteristic::Language(
332                        lg,
333                        FunctionLanguage::Sql(parser.consume()),
334                    ),
335                    Token::Ident(_, Keyword::PLPGSQL) => FunctionCharacteristic::Language(
336                        lg,
337                        FunctionLanguage::Plpgsql(parser.consume()),
338                    ),
339                    Token::Ident(_, _) if parser.options.dialect.is_postgresql() => {
340                        FunctionCharacteristic::Language(
341                            lg,
342                            FunctionLanguage::Other(parser.consume_plain_identifier_unreserved()?),
343                        )
344                    }
345                    _ => parser.expected_failure("language name")?,
346                }
347            }
348            Token::Ident(_, Keyword::NOT) => FunctionCharacteristic::NotDeterministic(
349                parser.consume_keywords(&[Keyword::NOT, Keyword::DETERMINISTIC])?,
350            ),
351            Token::Ident(_, Keyword::DETERMINISTIC) => FunctionCharacteristic::Deterministic(
352                parser.consume_keyword(Keyword::DETERMINISTIC)?,
353            ),
354            Token::Ident(_, Keyword::CONTAINS) => FunctionCharacteristic::ContainsSql(
355                parser.consume_keywords(&[Keyword::CONTAINS, Keyword::SQL])?,
356            ),
357            Token::Ident(_, Keyword::NO) => FunctionCharacteristic::NoSql(
358                parser.consume_keywords(&[Keyword::NO, Keyword::SQL])?,
359            ),
360            Token::Ident(_, Keyword::READS) => {
361                FunctionCharacteristic::ReadsSqlData(parser.consume_keywords(&[
362                    Keyword::READS,
363                    Keyword::SQL,
364                    Keyword::DATA,
365                ])?)
366            }
367            Token::Ident(_, Keyword::MODIFIES) => {
368                FunctionCharacteristic::ModifiesSqlData(parser.consume_keywords(&[
369                    Keyword::MODIFIES,
370                    Keyword::SQL,
371                    Keyword::DATA,
372                ])?)
373            }
374            Token::Ident(_, Keyword::COMMENT) => {
375                parser.consume_keyword(Keyword::COMMENT)?;
376                FunctionCharacteristic::Comment(parser.consume_string()?)
377            }
378            Token::Ident(_, Keyword::SQL) => {
379                let span = parser.consume_keywords(&[Keyword::SQL, Keyword::SECURITY])?;
380                match &parser.token {
381                    Token::Ident(_, Keyword::DEFINER) => {
382                        FunctionCharacteristic::SqlSecurityDefiner(
383                            parser.consume_keyword(Keyword::DEFINER)?.join_span(&span),
384                        )
385                    }
386                    Token::Ident(_, Keyword::USER) => FunctionCharacteristic::SqlSecurityUser(
387                        parser.consume_keyword(Keyword::USER)?.join_span(&span),
388                    ),
389                    _ => parser.expected_failure("'DEFINER' or 'USER'")?,
390                }
391            }
392            Token::Ident(_, Keyword::IMMUTABLE) => {
393                FunctionCharacteristic::Immutable(parser.consume_keyword(Keyword::IMMUTABLE)?)
394            }
395            Token::Ident(_, Keyword::STABLE) => {
396                FunctionCharacteristic::Stable(parser.consume_keyword(Keyword::STABLE)?)
397            }
398            Token::Ident(_, Keyword::VOLATILE) => {
399                FunctionCharacteristic::Volatile(parser.consume_keyword(Keyword::VOLATILE)?)
400            }
401            Token::Ident(_, Keyword::STRICT) => {
402                FunctionCharacteristic::Strict(parser.consume_keyword(Keyword::STRICT)?)
403            }
404            Token::Ident(_, Keyword::CALLED) if parser.options.dialect.is_postgresql() => {
405                FunctionCharacteristic::CalledOnNullInput(parser.consume_keywords(&[
406                    Keyword::CALLED,
407                    Keyword::ON,
408                    Keyword::NULL,
409                    Keyword::INPUT,
410                ])?)
411            }
412            Token::Ident(_, Keyword::RETURNS) if parser.options.dialect.is_postgresql() => {
413                FunctionCharacteristic::ReturnsNullOnNullInput(parser.consume_keywords(&[
414                    Keyword::RETURNS,
415                    Keyword::NULL,
416                    Keyword::ON,
417                    Keyword::NULL,
418                    Keyword::INPUT,
419                ])?)
420            }
421            Token::Ident(_, Keyword::PARALLEL) => {
422                let parallel_span = parser.consume_keyword(Keyword::PARALLEL)?;
423                let level = match parser.consume_plain_identifier_unreserved()?.value {
424                    v if v.eq_ignore_ascii_case("safe") => {
425                        FunctionParallel::Safe(parallel_span.clone())
426                    }
427                    v if v.eq_ignore_ascii_case("unsafe") => {
428                        FunctionParallel::Unsafe(parallel_span.clone())
429                    }
430                    v if v.eq_ignore_ascii_case("restricted") => {
431                        FunctionParallel::Restricted(parallel_span.clone())
432                    }
433                    _ => {
434                        parser.expected_error("SAFE, UNSAFE, or RESTRICTED");
435                        FunctionParallel::Unsafe(parallel_span.clone())
436                    }
437                };
438                FunctionCharacteristic::Parallel(parallel_span, level)
439            }
440            Token::Ident(_, Keyword::AS) if parser.options.dialect.is_postgresql() => {
441                let as_span = parser.consume_keyword(Keyword::AS)?;
442                let mut strings = Vec::new();
443                match &parser.token {
444                    Token::String(_, _) => {
445                        strings.push(parser.consume_string()?);
446                        // Handle comma-separated strings (e.g. C functions: 'MODULE_PATHNAME', 'func_name')
447                        while parser.skip_token(Token::Comma).is_some() {
448                            if matches!(&parser.token, Token::String(_, _)) {
449                                strings.push(parser.consume_string()?);
450                            } else {
451                                break;
452                            }
453                        }
454                    }
455                    _ => {
456                        parser.expected_error("'$$' or string");
457                    }
458                }
459                body = Some(FunctionBody { as_span, strings });
460                continue;
461            }
462            _ => break,
463        };
464        characteristics.push(f);
465    }
466
467    if parser.options.dialect.is_postgresql()
468        && body.is_some()
469        && !characteristics
470            .iter()
471            .any(|c| matches!(c, FunctionCharacteristic::Language(_, _)))
472    {
473        parser.expected_failure("LANGUAGE")?;
474    }
475
476    let return_ = if parser.options.dialect.is_maria() {
477        let old = core::mem::replace(&mut parser.permit_compound_statements, true);
478        let r = match parse_statement(parser)? {
479            Some(v) => Some(v),
480            None => parser.expected_failure("statement")?,
481        };
482        parser.permit_compound_statements = old;
483        r
484    } else if matches!(&parser.token, Token::Ident(_, Keyword::RETURN)) {
485        // PostgreSQL SQL/PSM inline function body: `RETURN <expr>`
486        match parse_statement(parser)? {
487            Some(v) => Some(v),
488            None => parser.expected_failure("statement after RETURN")?,
489        }
490    } else {
491        None
492    };
493
494    Ok(CreateFunction {
495        create_span,
496        create_options,
497        function_span,
498        if_not_exists,
499        name,
500        params,
501        return_type,
502        characteristics,
503        body,
504        return_,
505        returns_span,
506    })
507}
508
509/// Representation of a CREATE PROCEDURE statement
510///
511/// Like functions but without a RETURNS clause.
512#[derive(Clone, Debug)]
513pub struct CreateProcedure<'a> {
514    /// Span of "CREATE"
515    pub create_span: Span,
516    /// Options after "CREATE" (e.g. DEFINER=)
517    pub create_options: Vec<CreateOption<'a>>,
518    /// Span of "PROCEDURE"
519    pub procedure_span: Span,
520    /// Span of "IF NOT EXISTS" if specified
521    pub if_not_exists: Option<Span>,
522    /// Name of created procedure
523    pub name: Identifier<'a>,
524    /// Names and types of procedure parameters
525    pub params: Vec<FunctionParam<'a>>,
526    /// Characteristics (DETERMINISTIC, NO SQL, etc.)
527    pub characteristics: Vec<FunctionCharacteristic<'a>>,
528    /// Body statement (typically a BEGIN...END block)
529    pub body: Option<Statement<'a>>,
530}
531
532impl<'a> Spanned for CreateProcedure<'a> {
533    fn span(&self) -> Span {
534        self.create_span
535            .join_span(&self.create_options)
536            .join_span(&self.procedure_span)
537            .join_span(&self.if_not_exists)
538            .join_span(&self.name)
539            .join_span(&self.params)
540            .join_span(&self.characteristics)
541            .join_span(&self.body)
542    }
543}
544
545pub(crate) fn parse_create_procedure<'a>(
546    parser: &mut Parser<'a, '_>,
547    create_span: Span,
548    create_options: Vec<CreateOption<'a>>,
549) -> Result<CreateProcedure<'a>, ParseError> {
550    let procedure_span = parser.consume_keyword(Keyword::PROCEDURE)?;
551
552    let if_not_exists = if let Some(if_) = parser.skip_keyword(Keyword::IF) {
553        Some(
554            parser
555                .consume_keywords(&[Keyword::NOT, Keyword::EXISTS])?
556                .join_span(&if_),
557        )
558    } else {
559        None
560    };
561
562    let name = parser.consume_plain_identifier_unreserved()?;
563    let mut params = Vec::new();
564    parser.consume_token(Token::LParen)?;
565    parser.recovered("')'", &|t| t == &Token::RParen, |parser| {
566        loop {
567            if matches!(parser.token, Token::RParen) {
568                break;
569            }
570            let direction = match &parser.token {
571                Token::Ident(_, Keyword::IN) => {
572                    let in_ = parser.consume_keyword(Keyword::IN)?;
573                    if let Some(out) = parser.skip_keyword(Keyword::OUT) {
574                        Some(FunctionParamDirection::InOut(in_.join_span(&out)))
575                    } else {
576                        Some(FunctionParamDirection::In(in_))
577                    }
578                }
579                Token::Ident(_, Keyword::OUT) => Some(FunctionParamDirection::Out(
580                    parser.consume_keyword(Keyword::OUT)?,
581                )),
582                Token::Ident(_, Keyword::INOUT) => Some(FunctionParamDirection::InOut(
583                    parser.consume_keyword(Keyword::INOUT)?,
584                )),
585                _ => None,
586            };
587            let name = Some(parser.consume_plain_identifier_unreserved()?);
588            let type_ = parse_data_type(parser, DataTypeContext::FunctionParam)?;
589            params.push(FunctionParam {
590                direction,
591                name,
592                type_,
593                default: None,
594            });
595            if parser.skip_token(Token::Comma).is_none() {
596                break;
597            }
598        }
599        Ok(())
600    })?;
601    parser.consume_token(Token::RParen)?;
602
603    let mut characteristics = Vec::new();
604    loop {
605        let f = match &parser.token {
606            Token::Ident(_, Keyword::NOT) => FunctionCharacteristic::NotDeterministic(
607                parser.consume_keywords(&[Keyword::NOT, Keyword::DETERMINISTIC])?,
608            ),
609            Token::Ident(_, Keyword::DETERMINISTIC) => FunctionCharacteristic::Deterministic(
610                parser.consume_keyword(Keyword::DETERMINISTIC)?,
611            ),
612            Token::Ident(_, Keyword::CONTAINS) => FunctionCharacteristic::ContainsSql(
613                parser.consume_keywords(&[Keyword::CONTAINS, Keyword::SQL])?,
614            ),
615            Token::Ident(_, Keyword::NO) => FunctionCharacteristic::NoSql(
616                parser.consume_keywords(&[Keyword::NO, Keyword::SQL])?,
617            ),
618            Token::Ident(_, Keyword::READS) => {
619                FunctionCharacteristic::ReadsSqlData(parser.consume_keywords(&[
620                    Keyword::READS,
621                    Keyword::SQL,
622                    Keyword::DATA,
623                ])?)
624            }
625            Token::Ident(_, Keyword::MODIFIES) => {
626                FunctionCharacteristic::ModifiesSqlData(parser.consume_keywords(&[
627                    Keyword::MODIFIES,
628                    Keyword::SQL,
629                    Keyword::DATA,
630                ])?)
631            }
632            Token::Ident(_, Keyword::COMMENT) => {
633                parser.consume_keyword(Keyword::COMMENT)?;
634                FunctionCharacteristic::Comment(parser.consume_string()?)
635            }
636            Token::Ident(_, Keyword::SQL) => {
637                let span = parser.consume_keywords(&[Keyword::SQL, Keyword::SECURITY])?;
638                match &parser.token {
639                    Token::Ident(_, Keyword::DEFINER) => {
640                        FunctionCharacteristic::SqlSecurityDefiner(
641                            parser.consume_keyword(Keyword::DEFINER)?.join_span(&span),
642                        )
643                    }
644                    Token::Ident(_, Keyword::USER) => FunctionCharacteristic::SqlSecurityUser(
645                        parser.consume_keyword(Keyword::USER)?.join_span(&span),
646                    ),
647                    _ => parser.expected_failure("'DEFINER' or 'USER'")?,
648                }
649            }
650            _ => break,
651        };
652        characteristics.push(f);
653    }
654
655    let old = core::mem::replace(&mut parser.permit_compound_statements, true);
656    let body = parse_statement(parser)?;
657    parser.permit_compound_statements = old;
658
659    Ok(CreateProcedure {
660        create_span,
661        create_options,
662        procedure_span,
663        if_not_exists,
664        name,
665        params,
666        characteristics,
667        body,
668    })
669}