Skip to main content

radixdb_sql/ast/
procedural.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9use super::*;
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct SourceRange {
13    pub start: Position,
14    pub end: Position,
15}
16
17impl SourceRange {
18    pub const fn new(start: Position, end: Position) -> Self {
19        Self { start, end }
20    }
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub struct ObjectName {
25    pub components: Vec<Identifier>,
26}
27
28impl ObjectName {
29    pub fn new(components: Vec<Identifier>) -> Self {
30        Self { components }
31    }
32}
33
34impl fmt::Display for ObjectName {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        for (index, component) in self.components.iter().enumerate() {
37            if index > 0 {
38                formatter.write_str(".")?;
39            }
40            write!(formatter, "{component}")?;
41        }
42        Ok(())
43    }
44}
45
46#[derive(Debug, Clone, PartialEq)]
47pub enum ProceduralType {
48    Scalar(SmartString),
49    RowType(ObjectName),
50}
51
52impl fmt::Display for ProceduralType {
53    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::Scalar(name) => formatter.write_str(name),
56            Self::RowType(table) => write!(formatter, "{table}%ROWTYPE"),
57        }
58    }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum RoutineArgumentMode {
63    In,
64    Out,
65    InOut,
66}
67
68impl fmt::Display for RoutineArgumentMode {
69    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70        formatter.write_str(match self {
71            Self::In => "IN",
72            Self::Out => "OUT",
73            Self::InOut => "INOUT",
74        })
75    }
76}
77
78#[derive(Debug, Clone, PartialEq)]
79pub struct RoutineArgumentSyntax {
80    pub token: Token,
81    pub mode: RoutineArgumentMode,
82    pub name: Identifier,
83    pub data_type: ProceduralType,
84    pub nullable: bool,
85    pub default: Option<Expression>,
86    pub span: SourceRange,
87}
88
89impl fmt::Display for RoutineArgumentSyntax {
90    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
91        if self.mode != RoutineArgumentMode::In {
92            write!(formatter, "{} ", self.mode)?;
93        }
94        write!(formatter, "{} {}", self.name, self.data_type)?;
95        if !self.nullable {
96            formatter.write_str(" NOT NULL")?;
97        }
98        if let Some(default) = &self.default {
99            write!(formatter, " DEFAULT {default}")?;
100        }
101        Ok(())
102    }
103}
104
105#[derive(Debug, Clone, PartialEq)]
106pub struct ResultColumnSyntax {
107    pub name: Identifier,
108    pub data_type: ProceduralType,
109    pub nullable: bool,
110}
111
112impl fmt::Display for ResultColumnSyntax {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        write!(formatter, "{} {}", self.name, self.data_type)?;
115        if !self.nullable {
116            formatter.write_str(" NOT NULL")?;
117        }
118        Ok(())
119    }
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub enum RoutineReturnSyntax {
124    Scalar {
125        data_type: ProceduralType,
126        nullable: bool,
127    },
128    Table(Vec<ResultColumnSyntax>),
129    Trigger,
130}
131
132impl fmt::Display for RoutineReturnSyntax {
133    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
134        match self {
135            Self::Scalar {
136                data_type,
137                nullable,
138            } => {
139                write!(formatter, "{data_type}")?;
140                if !nullable {
141                    formatter.write_str(" NOT NULL")?;
142                }
143                Ok(())
144            }
145            Self::Table(columns) => {
146                formatter.write_str("TABLE (")?;
147                for (index, column) in columns.iter().enumerate() {
148                    if index > 0 {
149                        formatter.write_str(", ")?;
150                    }
151                    write!(formatter, "{column}")?;
152                }
153                formatter.write_str(")")
154            }
155            Self::Trigger => formatter.write_str("TRIGGER"),
156        }
157    }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum RoutineVolatilitySyntax {
162    Immutable,
163    Stable,
164    Volatile,
165}
166
167impl fmt::Display for RoutineVolatilitySyntax {
168    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
169        formatter.write_str(match self {
170            Self::Immutable => "IMMUTABLE",
171            Self::Stable => "STABLE",
172            Self::Volatile => "VOLATILE",
173        })
174    }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum RoutineSecuritySyntax {
179    Invoker,
180    Definer,
181}
182
183impl fmt::Display for RoutineSecuritySyntax {
184    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185        formatter.write_str(match self {
186            Self::Invoker => "SECURITY INVOKER",
187            Self::Definer => "SECURITY DEFINER",
188        })
189    }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum RoutineKindSyntax {
194    Function,
195    Procedure,
196}
197
198#[derive(Debug, Clone, PartialEq)]
199pub struct NativeFunctionBindingSyntax {
200    pub extension: ObjectName,
201    pub local_id: SmartString,
202}
203
204#[derive(Debug, Clone, PartialEq)]
205pub struct CreateRoutineStatement {
206    pub token: Token,
207    pub or_replace: bool,
208    pub kind: RoutineKindSyntax,
209    pub name: ObjectName,
210    pub arguments: Vec<RoutineArgumentSyntax>,
211    pub returns: Option<RoutineReturnSyntax>,
212    pub volatility: Option<RoutineVolatilitySyntax>,
213    pub security: RoutineSecuritySyntax,
214    pub search_path: Vec<ObjectName>,
215    pub resource_policy: Option<ObjectName>,
216    pub body: Option<ProceduralBlock>,
217    pub native: Option<NativeFunctionBindingSyntax>,
218    pub normalized_source: String,
219    pub span: SourceRange,
220}
221
222impl fmt::Display for CreateRoutineStatement {
223    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
224        formatter.write_str("CREATE ")?;
225        if self.or_replace {
226            formatter.write_str("OR REPLACE ")?;
227        }
228        let kind = match self.kind {
229            RoutineKindSyntax::Function => "FUNCTION",
230            RoutineKindSyntax::Procedure => "PROCEDURE",
231        };
232        write!(formatter, "{kind} {}(", self.name)?;
233        for (index, argument) in self.arguments.iter().enumerate() {
234            if index > 0 {
235                formatter.write_str(", ")?;
236            }
237            write!(formatter, "{argument}")?;
238        }
239        formatter.write_str(")")?;
240        if let Some(returns) = &self.returns {
241            write!(formatter, " RETURNS {returns}")?;
242        }
243        if let Some(native) = &self.native {
244            write!(
245                formatter,
246                " LANGUAGE NATIVE FROM EXTENSION {} AS '{}'",
247                native.extension,
248                escape_sql_string(native.local_id.as_str())
249            )?;
250            return Ok(());
251        }
252        formatter.write_str(" LANGUAGE RADIX")?;
253        if let Some(volatility) = self.volatility {
254            write!(formatter, " {volatility}")?;
255        }
256        write!(formatter, " {}", self.security)?;
257        if !self.search_path.is_empty() {
258            formatter.write_str(" SEARCH PATH (")?;
259            for (index, namespace) in self.search_path.iter().enumerate() {
260                if index > 0 {
261                    formatter.write_str(", ")?;
262                }
263                write!(formatter, "{namespace}")?;
264            }
265            formatter.write_str(")")?;
266        }
267        if let Some(policy) = &self.resource_policy {
268            write!(formatter, " RESOURCE POLICY {policy}")?;
269        }
270        write!(
271            formatter,
272            " AS {}",
273            self.body
274                .as_ref()
275                .expect("RADIX routine requires a procedural body")
276        )
277    }
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum TriggerTimingSyntax {
282    Before,
283    After,
284}
285
286impl fmt::Display for TriggerTimingSyntax {
287    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
288        formatter.write_str(match self {
289            Self::Before => "BEFORE",
290            Self::After => "AFTER",
291        })
292    }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum TriggerLevelSyntax {
297    Row,
298    Statement,
299}
300
301impl fmt::Display for TriggerLevelSyntax {
302    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
303        formatter.write_str(match self {
304            Self::Row => "ROW",
305            Self::Statement => "STATEMENT",
306        })
307    }
308}
309
310#[derive(Debug, Clone, PartialEq)]
311pub enum TriggerEventSyntax {
312    Insert,
313    Update { columns: Vec<Identifier> },
314    Delete,
315}
316
317impl fmt::Display for TriggerEventSyntax {
318    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
319        match self {
320            Self::Insert => formatter.write_str("INSERT"),
321            Self::Update { columns } => {
322                formatter.write_str("UPDATE")?;
323                if !columns.is_empty() {
324                    formatter.write_str(" OF ")?;
325                    write_identifiers(formatter, columns)?;
326                }
327                Ok(())
328            }
329            Self::Delete => formatter.write_str("DELETE"),
330        }
331    }
332}
333
334#[derive(Debug, Clone, PartialEq)]
335pub struct RoutineSignatureSyntax {
336    pub name: ObjectName,
337    pub argument_types: Vec<ProceduralType>,
338}
339
340impl fmt::Display for RoutineSignatureSyntax {
341    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
342        write!(formatter, "{}(", self.name)?;
343        for (index, argument_type) in self.argument_types.iter().enumerate() {
344            if index > 0 {
345                formatter.write_str(", ")?;
346            }
347            write!(formatter, "{argument_type}")?;
348        }
349        formatter.write_str(")")
350    }
351}
352
353#[derive(Debug, Clone, PartialEq)]
354pub struct CreateTriggerStatement {
355    pub token: Token,
356    pub or_replace: bool,
357    pub name: ObjectName,
358    pub timing: TriggerTimingSyntax,
359    pub events: Vec<TriggerEventSyntax>,
360    pub table: ObjectName,
361    pub level: TriggerLevelSyntax,
362    pub priority: i32,
363    pub when: Option<Expression>,
364    pub function: RoutineSignatureSyntax,
365    pub span: SourceRange,
366}
367
368impl fmt::Display for CreateTriggerStatement {
369    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
370        formatter.write_str("CREATE ")?;
371        if self.or_replace {
372            formatter.write_str("OR REPLACE ")?;
373        }
374        write!(formatter, "TRIGGER {} {} ", self.name, self.timing)?;
375        for (index, event) in self.events.iter().enumerate() {
376            if index > 0 {
377                formatter.write_str(" OR ")?;
378            }
379            write!(formatter, "{event}")?;
380        }
381        write!(
382            formatter,
383            " ON {} FOR EACH {} PRIORITY {}",
384            self.table, self.level, self.priority
385        )?;
386        if let Some(condition) = &self.when {
387            write!(formatter, " WHEN ({condition})")?;
388        }
389        write!(formatter, " EXECUTE FUNCTION {}", self.function)
390    }
391}
392
393#[derive(Debug, Clone, PartialEq)]
394pub enum JobScheduleSyntax {
395    Every(Expression),
396    At(Expression),
397}
398
399#[derive(Debug, Clone, PartialEq)]
400pub struct CreateJobStatement {
401    pub token: Token,
402    pub name: ObjectName,
403    pub schedule: JobScheduleSyntax,
404    pub principal: ObjectName,
405    pub procedure: ObjectName,
406    pub arguments: Vec<CallArgumentSyntax>,
407    pub enabled: bool,
408    pub span: SourceRange,
409}
410
411impl fmt::Display for CreateJobStatement {
412    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
413        write!(formatter, "CREATE JOB {} SCHEDULE ", self.name)?;
414        match &self.schedule {
415            JobScheduleSyntax::Every(interval) => write!(formatter, "EVERY {interval}")?,
416            JobScheduleSyntax::At(timestamp) => write!(formatter, "AT {timestamp}")?,
417        }
418        write!(
419            formatter,
420            " RUN AS {} CALL {}(",
421            self.principal, self.procedure
422        )?;
423        for (index, argument) in self.arguments.iter().enumerate() {
424            if index > 0 {
425                formatter.write_str(", ")?;
426            }
427            write!(formatter, "{argument}")?;
428        }
429        formatter.write_str(if self.enabled {
430            ") ENABLE"
431        } else {
432            ") DISABLE"
433        })
434    }
435}
436
437#[derive(Debug, Clone, PartialEq)]
438pub struct DropRoutineStatement {
439    pub token: Token,
440    pub kind: RoutineKindSyntax,
441    pub signature: RoutineSignatureSyntax,
442    pub if_exists: bool,
443    pub behavior: DropBehaviorSyntax,
444}
445
446impl fmt::Display for DropRoutineStatement {
447    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
448        formatter.write_str(match self.kind {
449            RoutineKindSyntax::Function => "DROP FUNCTION ",
450            RoutineKindSyntax::Procedure => "DROP PROCEDURE ",
451        })?;
452        if self.if_exists {
453            formatter.write_str("IF EXISTS ")?;
454        }
455        write!(formatter, "{} {}", self.signature, self.behavior)
456    }
457}
458
459#[derive(Debug, Clone, PartialEq)]
460pub struct DropTriggerStatement {
461    pub token: Token,
462    pub name: ObjectName,
463    pub table: ObjectName,
464    pub if_exists: bool,
465    pub behavior: DropBehaviorSyntax,
466}
467
468impl fmt::Display for DropTriggerStatement {
469    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
470        formatter.write_str("DROP TRIGGER ")?;
471        if self.if_exists {
472            formatter.write_str("IF EXISTS ")?;
473        }
474        write!(
475            formatter,
476            "{} ON {} {}",
477            self.name, self.table, self.behavior
478        )
479    }
480}
481
482#[derive(Debug, Clone, PartialEq)]
483pub struct DropJobStatement {
484    pub token: Token,
485    pub name: ObjectName,
486    pub if_exists: bool,
487    pub behavior: DropBehaviorSyntax,
488}
489
490impl fmt::Display for DropJobStatement {
491    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
492        formatter.write_str("DROP JOB ")?;
493        if self.if_exists {
494            formatter.write_str("IF EXISTS ")?;
495        }
496        write!(formatter, "{} {}", self.name, self.behavior)
497    }
498}
499
500#[derive(Debug, Clone, PartialEq)]
501pub struct AlterJobStatement {
502    pub token: Token,
503    pub name: ObjectName,
504    pub enabled: bool,
505}
506
507impl fmt::Display for AlterJobStatement {
508    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
509        write!(
510            formatter,
511            "ALTER JOB {} {}",
512            self.name,
513            if self.enabled { "ENABLE" } else { "DISABLE" }
514        )
515    }
516}
517
518fn write_identifiers(
519    formatter: &mut fmt::Formatter<'_>,
520    identifiers: &[Identifier],
521) -> fmt::Result {
522    for (index, identifier) in identifiers.iter().enumerate() {
523        if index > 0 {
524            formatter.write_str(", ")?;
525        }
526        write!(formatter, "{identifier}")?;
527    }
528    Ok(())
529}
530
531#[derive(Debug, Clone, PartialEq)]
532pub enum ProceduralDeclaration {
533    Variable {
534        token: Token,
535        name: Identifier,
536        constant: bool,
537        data_type: ProceduralType,
538        nullable: bool,
539        initializer: Option<Expression>,
540        span: SourceRange,
541    },
542    Collection {
543        token: Token,
544        name: Identifier,
545        element_type: ProceduralType,
546        capacity: u32,
547        span: SourceRange,
548    },
549    Cursor {
550        token: Token,
551        name: Identifier,
552        arguments: Vec<RoutineArgumentSyntax>,
553        query: Box<Statement>,
554        span: SourceRange,
555    },
556}
557
558impl fmt::Display for ProceduralDeclaration {
559    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
560        match self {
561            Self::Variable {
562                name,
563                constant,
564                data_type,
565                nullable,
566                initializer,
567                ..
568            } => {
569                write!(formatter, "{name} ")?;
570                if *constant {
571                    formatter.write_str("CONSTANT ")?;
572                }
573                write!(formatter, "{data_type}")?;
574                if !nullable {
575                    formatter.write_str(" NOT NULL")?;
576                }
577                if let Some(initializer) = initializer {
578                    write!(formatter, " := {initializer}")?;
579                }
580                Ok(())
581            }
582            Self::Collection {
583                name,
584                element_type,
585                capacity,
586                ..
587            } => write!(formatter, "{name} ARRAY<{element_type}, {capacity}>"),
588            Self::Cursor {
589                name,
590                arguments,
591                query,
592                ..
593            } => {
594                write!(formatter, "CURSOR {name}(")?;
595                for (index, argument) in arguments.iter().enumerate() {
596                    if index > 0 {
597                        formatter.write_str(", ")?;
598                    }
599                    write!(formatter, "{} {}", argument.name, argument.data_type)?;
600                }
601                write!(formatter, ") FOR {query}")
602            }
603        }
604    }
605}
606
607#[derive(Debug, Clone, PartialEq)]
608pub struct CallArgumentSyntax {
609    pub name: Option<Identifier>,
610    pub value: Expression,
611}
612
613#[derive(Debug, Clone, PartialEq)]
614pub struct CallStatement {
615    pub token: Token,
616    pub routine: ObjectName,
617    pub arguments: Vec<CallArgumentSyntax>,
618}
619
620impl fmt::Display for CallStatement {
621    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
622        write!(formatter, "CALL {}(", self.routine)?;
623        for (index, argument) in self.arguments.iter().enumerate() {
624            if index > 0 {
625                formatter.write_str(", ")?;
626            }
627            write!(formatter, "{argument}")?;
628        }
629        formatter.write_str(")")
630    }
631}
632
633impl fmt::Display for CallArgumentSyntax {
634    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
635        if let Some(name) = &self.name {
636            write!(formatter, "{name} => ")?;
637        }
638        write!(formatter, "{}", self.value)
639    }
640}
641
642#[derive(Debug, Clone, PartialEq)]
643pub enum AssignmentTargetSyntax {
644    Name(ObjectName),
645    Index {
646        collection: ObjectName,
647        index: Expression,
648    },
649}
650
651impl fmt::Display for AssignmentTargetSyntax {
652    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
653        match self {
654            Self::Name(name) => write!(formatter, "{name}"),
655            Self::Index { collection, index } => write!(formatter, "{collection}[{index}]"),
656        }
657    }
658}
659
660#[derive(Debug, Clone, PartialEq)]
661pub struct ProceduralSqlStatement {
662    pub statement: Box<Statement>,
663    pub into: Vec<Identifier>,
664    pub strict: bool,
665    pub span: SourceRange,
666}
667
668#[derive(Debug, Clone, PartialEq)]
669pub enum ReturnSyntax {
670    Void,
671    Value(Expression),
672    Next(Vec<Expression>),
673    Query(Box<Statement>),
674}
675
676#[derive(Debug, Clone, PartialEq)]
677pub enum LoopControlKind {
678    Exit,
679    Continue,
680}
681
682#[derive(Debug, Clone, PartialEq)]
683pub enum ForSourceSyntax {
684    Numeric {
685        reverse: bool,
686        start: Box<Expression>,
687        end: Box<Expression>,
688        step: Option<Box<Expression>>,
689    },
690    Query(Box<Statement>),
691}
692
693#[derive(Debug, Clone, PartialEq)]
694pub struct CaseArmSyntax {
695    pub condition: Expression,
696    pub statements: Vec<ProceduralStatement>,
697}
698
699#[derive(Debug, Clone, PartialEq)]
700pub struct DynamicExecuteSyntax {
701    pub source: Expression,
702    pub into: Vec<Identifier>,
703    pub strict: bool,
704    pub using: Vec<Expression>,
705}
706
707#[derive(Debug, Clone, PartialEq)]
708pub enum ProceduralStatement {
709    Assignment {
710        token: Token,
711        target: AssignmentTargetSyntax,
712        value: Expression,
713        span: SourceRange,
714    },
715    Call {
716        token: Token,
717        routine: ObjectName,
718        arguments: Vec<CallArgumentSyntax>,
719        span: SourceRange,
720    },
721    Perform {
722        token: Token,
723        expression: Expression,
724        span: SourceRange,
725    },
726    If {
727        token: Token,
728        branches: Vec<(Expression, Vec<ProceduralStatement>)>,
729        otherwise: Vec<ProceduralStatement>,
730        span: SourceRange,
731    },
732    Case {
733        token: Token,
734        operand: Option<Expression>,
735        arms: Vec<CaseArmSyntax>,
736        otherwise: Vec<ProceduralStatement>,
737        span: SourceRange,
738    },
739    Loop {
740        token: Token,
741        statements: Vec<ProceduralStatement>,
742        span: SourceRange,
743    },
744    While {
745        token: Token,
746        condition: Expression,
747        statements: Vec<ProceduralStatement>,
748        span: SourceRange,
749    },
750    For {
751        token: Token,
752        variable: Identifier,
753        source: ForSourceSyntax,
754        statements: Vec<ProceduralStatement>,
755        span: SourceRange,
756    },
757    LoopControl {
758        token: Token,
759        kind: LoopControlKind,
760        condition: Option<Expression>,
761        span: SourceRange,
762    },
763    Return {
764        token: Token,
765        value: ReturnSyntax,
766        span: SourceRange,
767    },
768    Sql(ProceduralSqlStatement),
769    DynamicExecute {
770        token: Token,
771        execute: DynamicExecuteSyntax,
772        span: SourceRange,
773    },
774    OpenCursor {
775        token: Token,
776        cursor: Identifier,
777        arguments: Vec<Expression>,
778        span: SourceRange,
779    },
780    FetchCursor {
781        token: Token,
782        cursor: Identifier,
783        into: Vec<Identifier>,
784        span: SourceRange,
785    },
786    CloseCursor {
787        token: Token,
788        cursor: Identifier,
789        span: SourceRange,
790    },
791    Raise {
792        token: Token,
793        kind: Option<Identifier>,
794        arguments: Vec<Expression>,
795        span: SourceRange,
796    },
797    Block(Box<ProceduralBlock>),
798}
799
800#[derive(Debug, Clone, PartialEq)]
801pub enum ExceptionPatternSyntax {
802    Named(Identifier),
803    Others,
804}
805
806impl fmt::Display for ExceptionPatternSyntax {
807    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
808        match self {
809            Self::Named(name) => write!(formatter, "{name}"),
810            Self::Others => formatter.write_str("OTHERS"),
811        }
812    }
813}
814
815#[derive(Debug, Clone, PartialEq)]
816pub struct ExceptionHandlerSyntax {
817    pub token: Token,
818    pub patterns: Vec<ExceptionPatternSyntax>,
819    pub alias: Option<Identifier>,
820    pub statements: Vec<ProceduralStatement>,
821    pub span: SourceRange,
822}
823
824#[derive(Debug, Clone, PartialEq)]
825pub struct ProceduralBlock {
826    pub token: Token,
827    pub declarations: Vec<ProceduralDeclaration>,
828    pub statements: Vec<ProceduralStatement>,
829    pub handlers: Vec<ExceptionHandlerSyntax>,
830    pub span: SourceRange,
831}
832
833impl fmt::Display for ProceduralBlock {
834    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
835        if !self.declarations.is_empty() {
836            formatter.write_str("DECLARE ")?;
837            for declaration in &self.declarations {
838                write!(formatter, "{declaration}; ")?;
839            }
840        }
841        formatter.write_str("BEGIN ")?;
842        for statement in &self.statements {
843            write!(formatter, "{statement}; ")?;
844        }
845        if !self.handlers.is_empty() {
846            formatter.write_str("EXCEPTION ")?;
847            for handler in &self.handlers {
848                formatter.write_str("WHEN ")?;
849                for (index, pattern) in handler.patterns.iter().enumerate() {
850                    if index > 0 {
851                        formatter.write_str(" OR ")?;
852                    }
853                    write!(formatter, "{pattern}")?;
854                }
855                if let Some(alias) = &handler.alias {
856                    write!(formatter, " AS {alias}")?;
857                }
858                formatter.write_str(" THEN ")?;
859                for statement in &handler.statements {
860                    write!(formatter, "{statement}; ")?;
861                }
862            }
863        }
864        formatter.write_str("END")
865    }
866}
867
868impl fmt::Display for ProceduralStatement {
869    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
870        match self {
871            Self::Assignment { target, value, .. } => write!(formatter, "{target} := {value}"),
872            Self::Call {
873                routine, arguments, ..
874            } => {
875                write!(formatter, "CALL {routine}(")?;
876                for (index, argument) in arguments.iter().enumerate() {
877                    if index > 0 {
878                        formatter.write_str(", ")?;
879                    }
880                    write!(formatter, "{argument}")?;
881                }
882                formatter.write_str(")")
883            }
884            Self::Perform { expression, .. } => write!(formatter, "PERFORM {expression}"),
885            Self::If {
886                branches,
887                otherwise,
888                ..
889            } => {
890                for (index, (condition, statements)) in branches.iter().enumerate() {
891                    if index == 0 {
892                        write!(formatter, "IF {condition} THEN ")?;
893                    } else {
894                        write!(formatter, "ELSIF {condition} THEN ")?;
895                    }
896                    for statement in statements {
897                        write!(formatter, "{statement}; ")?;
898                    }
899                }
900                if !otherwise.is_empty() {
901                    formatter.write_str("ELSE ")?;
902                    for statement in otherwise {
903                        write!(formatter, "{statement}; ")?;
904                    }
905                }
906                formatter.write_str("END IF")
907            }
908            Self::Case {
909                operand,
910                arms,
911                otherwise,
912                ..
913            } => {
914                formatter.write_str("CASE")?;
915                if let Some(operand) = operand {
916                    write!(formatter, " {operand}")?;
917                }
918                formatter.write_str(" ")?;
919                for arm in arms {
920                    write!(formatter, "WHEN {} THEN ", arm.condition)?;
921                    for statement in &arm.statements {
922                        write!(formatter, "{statement}; ")?;
923                    }
924                }
925                if !otherwise.is_empty() {
926                    formatter.write_str("ELSE ")?;
927                    for statement in otherwise {
928                        write!(formatter, "{statement}; ")?;
929                    }
930                }
931                formatter.write_str("END CASE")
932            }
933            Self::Loop { statements, .. } => {
934                formatter.write_str("LOOP ")?;
935                for statement in statements {
936                    write!(formatter, "{statement}; ")?;
937                }
938                formatter.write_str("END LOOP")
939            }
940            Self::While {
941                condition,
942                statements,
943                ..
944            } => {
945                write!(formatter, "WHILE {condition} LOOP ")?;
946                for statement in statements {
947                    write!(formatter, "{statement}; ")?;
948                }
949                formatter.write_str("END LOOP")
950            }
951            Self::For {
952                variable,
953                source,
954                statements,
955                ..
956            } => {
957                write!(formatter, "FOR {variable} IN ")?;
958                match source {
959                    ForSourceSyntax::Numeric {
960                        reverse,
961                        start,
962                        end,
963                        step,
964                    } => {
965                        if *reverse {
966                            formatter.write_str("REVERSE ")?;
967                        }
968                        write!(formatter, "{start} TO {end}")?;
969                        if let Some(step) = step {
970                            write!(formatter, " BY {step}")?;
971                        }
972                    }
973                    ForSourceSyntax::Query(query) => write!(formatter, "({query})")?,
974                }
975                formatter.write_str(" LOOP ")?;
976                for statement in statements {
977                    write!(formatter, "{statement}; ")?;
978                }
979                formatter.write_str("END LOOP")
980            }
981            Self::LoopControl {
982                kind, condition, ..
983            } => {
984                formatter.write_str(match kind {
985                    LoopControlKind::Exit => "EXIT",
986                    LoopControlKind::Continue => "CONTINUE",
987                })?;
988                if let Some(condition) = condition {
989                    write!(formatter, " WHEN {condition}")?;
990                }
991                Ok(())
992            }
993            Self::Return { value, .. } => match value {
994                ReturnSyntax::Void => formatter.write_str("RETURN"),
995                ReturnSyntax::Value(value) => write!(formatter, "RETURN {value}"),
996                ReturnSyntax::Next(values) => {
997                    formatter.write_str("RETURN NEXT (")?;
998                    for (index, value) in values.iter().enumerate() {
999                        if index > 0 {
1000                            formatter.write_str(", ")?;
1001                        }
1002                        write!(formatter, "{value}")?;
1003                    }
1004                    formatter.write_str(")")
1005                }
1006                ReturnSyntax::Query(query) => write!(formatter, "RETURN QUERY {query}"),
1007            },
1008            Self::Sql(sql) => {
1009                let rendered = sql.statement.to_string();
1010                if sql.into.is_empty() {
1011                    return formatter.write_str(&rendered);
1012                }
1013                if matches!(sql.statement.as_ref(), Statement::Select(_)) {
1014                    if let Some(offset) = top_level_from_offset(&rendered) {
1015                        formatter.write_str(rendered[..offset].trim_end())?;
1016                        write_into_targets(formatter, sql.strict, &sql.into)?;
1017                        formatter.write_str(" ")?;
1018                        formatter.write_str(&rendered[offset..])?;
1019                    } else {
1020                        formatter.write_str(&rendered)?;
1021                        write_into_targets(formatter, sql.strict, &sql.into)?;
1022                    }
1023                } else {
1024                    formatter.write_str(&rendered)?;
1025                    write_into_targets(formatter, sql.strict, &sql.into)?;
1026                }
1027                Ok(())
1028            }
1029            Self::DynamicExecute { execute, .. } => {
1030                write!(formatter, "EXECUTE {}", execute.source)?;
1031                if !execute.into.is_empty() {
1032                    formatter.write_str(" INTO ")?;
1033                    if execute.strict {
1034                        formatter.write_str("STRICT ")?;
1035                    }
1036                    for (index, target) in execute.into.iter().enumerate() {
1037                        if index > 0 {
1038                            formatter.write_str(", ")?;
1039                        }
1040                        write!(formatter, "{target}")?;
1041                    }
1042                }
1043                if !execute.using.is_empty() {
1044                    formatter.write_str(" USING ")?;
1045                    for (index, argument) in execute.using.iter().enumerate() {
1046                        if index > 0 {
1047                            formatter.write_str(", ")?;
1048                        }
1049                        write!(formatter, "{argument}")?;
1050                    }
1051                }
1052                Ok(())
1053            }
1054            Self::OpenCursor {
1055                cursor, arguments, ..
1056            } => {
1057                write!(formatter, "OPEN {cursor}(")?;
1058                for (index, argument) in arguments.iter().enumerate() {
1059                    if index > 0 {
1060                        formatter.write_str(", ")?;
1061                    }
1062                    write!(formatter, "{argument}")?;
1063                }
1064                formatter.write_str(")")
1065            }
1066            Self::FetchCursor { cursor, into, .. } => {
1067                write!(formatter, "FETCH {cursor} INTO ")?;
1068                for (index, target) in into.iter().enumerate() {
1069                    if index > 0 {
1070                        formatter.write_str(", ")?;
1071                    }
1072                    write!(formatter, "{target}")?;
1073                }
1074                Ok(())
1075            }
1076            Self::CloseCursor { cursor, .. } => write!(formatter, "CLOSE {cursor}"),
1077            Self::Raise {
1078                kind, arguments, ..
1079            } => {
1080                formatter.write_str("RAISE")?;
1081                if let Some(kind) = kind {
1082                    write!(formatter, " {kind}(")?;
1083                    for (index, argument) in arguments.iter().enumerate() {
1084                        if index > 0 {
1085                            formatter.write_str(", ")?;
1086                        }
1087                        write!(formatter, "{argument}")?;
1088                    }
1089                    formatter.write_str(")")?;
1090                }
1091                Ok(())
1092            }
1093            Self::Block(block) => write!(formatter, "{block}"),
1094        }
1095    }
1096}
1097
1098fn write_into_targets(
1099    formatter: &mut fmt::Formatter<'_>,
1100    strict: bool,
1101    targets: &[Identifier],
1102) -> fmt::Result {
1103    formatter.write_str(" INTO ")?;
1104    if strict {
1105        formatter.write_str("STRICT ")?;
1106    }
1107    for (index, target) in targets.iter().enumerate() {
1108        if index > 0 {
1109            formatter.write_str(", ")?;
1110        }
1111        write!(formatter, "{target}")?;
1112    }
1113    Ok(())
1114}
1115
1116fn top_level_from_offset(sql: &str) -> Option<usize> {
1117    let mut lexer = crate::Lexer::new(sql);
1118    let mut depth = 0usize;
1119    loop {
1120        let token = lexer.next_token();
1121        if token.is_eof() {
1122            return None;
1123        }
1124        if token.is_punctuator("(") {
1125            depth += 1;
1126        } else if token.is_punctuator(")") {
1127            depth = depth.saturating_sub(1);
1128        } else if depth == 0 && token.is_keyword("FROM") {
1129            return Some(token.position.offset);
1130        }
1131    }
1132}