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