Skip to main content

radixdb_sql/ast/
ddl.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//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17/// Bind one exact already-installed package to the current database.
18#[derive(Debug, Clone, PartialEq)]
19pub struct CreateExtensionStatement {
20    pub token: Token,
21    pub name: Identifier,
22    pub version: SmartString,
23    pub if_not_exists: bool,
24}
25
26impl fmt::Display for CreateExtensionStatement {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "CREATE EXTENSION ")?;
29        if self.if_not_exists {
30            write!(f, "IF NOT EXISTS ")?;
31        }
32        write!(
33            f,
34            "{} VERSION '{}'",
35            self.name,
36            escape_sql_string(&self.version)
37        )
38    }
39}
40
41/// Remove only an extension binding with no dependent catalog objects.
42#[derive(Debug, Clone, PartialEq)]
43pub struct DropExtensionStatement {
44    pub token: Token,
45    pub name: Identifier,
46    pub if_exists: bool,
47}
48
49impl fmt::Display for DropExtensionStatement {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "DROP EXTENSION ")?;
52        if self.if_exists {
53            write!(f, "IF EXISTS ")?;
54        }
55        write!(f, "{} RESTRICT", self.name)
56    }
57}
58
59/// Bind one installed package type descriptor to a schema-qualified SQL name.
60#[derive(Debug, Clone, PartialEq)]
61pub struct CreateExternalTypeStatement {
62    pub token: Token,
63    pub name: ObjectName,
64    pub extension_name: Identifier,
65    pub local_id: SmartString,
66}
67
68impl fmt::Display for CreateExternalTypeStatement {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(
71            f,
72            "CREATE TYPE {} FROM EXTENSION {} AS '{}'",
73            self.name,
74            self.extension_name,
75            escape_sql_string(&self.local_id)
76        )
77    }
78}
79
80/// Remove an external SQL type only when it has no dependent objects.
81#[derive(Debug, Clone, PartialEq)]
82pub struct DropExternalTypeStatement {
83    pub token: Token,
84    pub name: ObjectName,
85    pub if_exists: bool,
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub struct QualifiedOperator {
90    pub schema: Identifier,
91    pub symbol: SmartString,
92}
93
94impl fmt::Display for QualifiedOperator {
95    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(formatter, "{}.{}", self.schema, self.symbol)
97    }
98}
99
100#[derive(Debug, Clone, PartialEq)]
101pub struct CreateOperatorStatement {
102    pub token: Token,
103    pub name: QualifiedOperator,
104    pub left_argument: Option<ProceduralType>,
105    pub right_argument: ProceduralType,
106    pub function: RoutineSignatureSyntax,
107    pub extension_name: Identifier,
108    pub local_id: SmartString,
109}
110
111impl fmt::Display for CreateOperatorStatement {
112    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(formatter, "CREATE OPERATOR {} (", self.name)?;
114        if let Some(left) = &self.left_argument {
115            write!(formatter, "LEFTARG = {left}, ")?;
116        }
117        write!(
118            formatter,
119            "RIGHTARG = {}, FUNCTION = {}) FROM EXTENSION {} AS '{}'",
120            self.right_argument,
121            self.function,
122            self.extension_name,
123            escape_sql_string(&self.local_id)
124        )
125    }
126}
127
128#[derive(Debug, Clone, PartialEq)]
129pub struct DropOperatorStatement {
130    pub token: Token,
131    pub name: QualifiedOperator,
132    pub left_argument: Option<ProceduralType>,
133    pub right_argument: Option<ProceduralType>,
134    pub if_exists: bool,
135}
136
137impl fmt::Display for DropOperatorStatement {
138    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
139        formatter.write_str("DROP OPERATOR ")?;
140        if self.if_exists {
141            formatter.write_str("IF EXISTS ")?;
142        }
143        write!(formatter, "{} (", self.name)?;
144        if let Some(left) = &self.left_argument {
145            write!(formatter, "{left}")?;
146        }
147        formatter.write_str(", ")?;
148        if let Some(right) = &self.right_argument {
149            write!(formatter, "{right}")?;
150        }
151        formatter.write_str(") RESTRICT")
152    }
153}
154
155#[derive(Debug, Clone, PartialEq)]
156pub struct CreateOperatorClassStatement {
157    pub token: Token,
158    pub name: ObjectName,
159    pub input_type: ProceduralType,
160    pub access_method: IndexMethod,
161    pub extension_name: Identifier,
162    pub local_id: SmartString,
163}
164
165impl fmt::Display for CreateOperatorClassStatement {
166    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167        write!(
168            formatter,
169            "CREATE OPERATOR CLASS {} FOR TYPE {} USING {} FROM EXTENSION {} AS '{}'",
170            self.name,
171            self.input_type,
172            self.access_method,
173            self.extension_name,
174            escape_sql_string(&self.local_id)
175        )
176    }
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub struct DropOperatorClassStatement {
181    pub token: Token,
182    pub name: ObjectName,
183    pub access_method: IndexMethod,
184    pub if_exists: bool,
185}
186
187#[derive(Debug, Clone, PartialEq)]
188pub struct CreatePlannerSupportStatement {
189    pub token: Token,
190    pub name: ObjectName,
191    pub function: RoutineSignatureSyntax,
192    pub extension_name: Identifier,
193    pub local_id: SmartString,
194}
195
196impl fmt::Display for CreatePlannerSupportStatement {
197    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
198        write!(
199            formatter,
200            "CREATE PLANNER SUPPORT {} FOR FUNCTION {} FROM EXTENSION {} AS '{}'",
201            self.name,
202            self.function,
203            self.extension_name,
204            escape_sql_string(&self.local_id)
205        )
206    }
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub struct DropPlannerSupportStatement {
211    pub token: Token,
212    pub name: ObjectName,
213    pub if_exists: bool,
214}
215
216impl fmt::Display for DropPlannerSupportStatement {
217    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218        formatter.write_str("DROP PLANNER SUPPORT ")?;
219        if self.if_exists {
220            formatter.write_str("IF EXISTS ")?;
221        }
222        write!(formatter, "{} RESTRICT", self.name)
223    }
224}
225
226impl fmt::Display for DropOperatorClassStatement {
227    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228        formatter.write_str("DROP OPERATOR CLASS ")?;
229        if self.if_exists {
230            formatter.write_str("IF EXISTS ")?;
231        }
232        write!(
233            formatter,
234            "{} USING {} RESTRICT",
235            self.name, self.access_method
236        )
237    }
238}
239
240impl fmt::Display for DropExternalTypeStatement {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(f, "DROP TYPE ")?;
243        if self.if_exists {
244            write!(f, "IF EXISTS ")?;
245        }
246        write!(f, "{} RESTRICT", self.name)
247    }
248}
249
250/// CREATE TABLE statement
251#[derive(Debug, Clone, PartialEq)]
252pub struct CreateTableStatement {
253    pub token: Token,
254    pub table_name: Identifier,
255    pub if_not_exists: bool,
256    pub columns: Vec<ColumnDefinition>,
257    /// Table-level constraints (UNIQUE(cols), CHECK(expr), etc.)
258    pub table_constraints: Vec<TableConstraint>,
259    /// Optional SELECT statement for CREATE TABLE ... AS SELECT
260    pub as_select: Option<Box<SelectStatement>>,
261}
262
263impl fmt::Display for CreateTableStatement {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        let mut result = String::from("CREATE TABLE ");
266        if self.if_not_exists {
267            result.push_str("IF NOT EXISTS ");
268        }
269        if let Some(ref select) = self.as_select {
270            result.push_str(&format!("{} AS {}", self.table_name, select));
271            return write!(f, "{}", result);
272        }
273        result.push_str(&format!("{} (", self.table_name));
274        let cols: Vec<String> = self.columns.iter().map(|c| c.to_string()).collect();
275        result.push_str(&cols.join(", "));
276        if !self.table_constraints.is_empty() {
277            let constraints: Vec<String> = self
278                .table_constraints
279                .iter()
280                .map(|c| c.to_string())
281                .collect();
282            result.push_str(", ");
283            result.push_str(&constraints.join(", "));
284        }
285        result.push(')');
286        write!(f, "{}", result)
287    }
288}
289
290/// Column definition
291#[derive(Debug, Clone, PartialEq)]
292pub struct ColumnDefinition {
293    pub name: Identifier,
294    pub data_type: SmartString,
295    pub constraints: Vec<ColumnConstraint>,
296}
297
298impl fmt::Display for ColumnDefinition {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        let mut result = format!("{} {}", self.name, self.data_type);
301        for constraint in &self.constraints {
302            result.push_str(&format!(" {}", constraint));
303        }
304        write!(f, "{}", result)
305    }
306}
307
308/// Column constraint
309#[derive(Debug, Clone, PartialEq)]
310pub enum ColumnConstraint {
311    NotNull,
312    PrimaryKey,
313    Unique,
314    AutoIncrement,
315    Default(Expression),
316    Check(Expression),
317    References {
318        table: Identifier,
319        column: Option<Identifier>,
320        on_delete: ForeignKeyAction,
321        on_update: ForeignKeyAction,
322    },
323}
324
325impl fmt::Display for ColumnConstraint {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        match self {
328            ColumnConstraint::NotNull => write!(f, "NOT NULL"),
329            ColumnConstraint::PrimaryKey => write!(f, "PRIMARY KEY"),
330            ColumnConstraint::Unique => write!(f, "UNIQUE"),
331            ColumnConstraint::AutoIncrement => write!(f, "AUTO_INCREMENT"),
332            ColumnConstraint::Default(expr) => write!(f, "DEFAULT {}", expr),
333            ColumnConstraint::Check(expr) => write!(f, "CHECK ({})", expr),
334            ColumnConstraint::References {
335                table,
336                column,
337                on_delete,
338                on_update,
339            } => {
340                write!(f, "REFERENCES {}", table)?;
341                if let Some(col) = column {
342                    write!(f, "({})", col)?;
343                }
344                if *on_delete != ForeignKeyAction::Restrict {
345                    write!(f, " ON DELETE {}", on_delete)?;
346                }
347                if *on_update != ForeignKeyAction::Restrict {
348                    write!(f, " ON UPDATE {}", on_update)?;
349                }
350                Ok(())
351            }
352        }
353    }
354}
355
356/// Table-level constraint (applied to the table rather than a single column)
357#[derive(Debug, Clone, PartialEq)]
358pub enum TableConstraint {
359    /// UNIQUE(col1, col2, ...)
360    Unique(Vec<Identifier>),
361    /// CHECK(expression) - boxed to reduce enum size
362    Check(Box<Expression>),
363    /// PRIMARY KEY(col1, col2, ...) - composite primary key (not yet fully supported)
364    PrimaryKey(Vec<Identifier>),
365    /// FOREIGN KEY(col) REFERENCES table(col) ON DELETE ... ON UPDATE ...
366    /// Boxed to reduce enum size (Identifier is large)
367    ForeignKey(Box<ForeignKeyTableConstraint>),
368}
369
370/// Fields for a table-level FOREIGN KEY constraint (boxed to reduce TableConstraint enum size)
371#[derive(Debug, Clone, PartialEq)]
372pub struct ForeignKeyTableConstraint {
373    pub column: Identifier,
374    pub ref_table: Identifier,
375    pub ref_column: Option<Identifier>,
376    pub on_delete: ForeignKeyAction,
377    pub on_update: ForeignKeyAction,
378}
379
380impl fmt::Display for TableConstraint {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        match self {
383            TableConstraint::Unique(cols) => {
384                let col_names: Vec<&str> = cols.iter().map(|c| c.value.as_str()).collect();
385                write!(f, "UNIQUE({})", col_names.join(", "))
386            }
387            TableConstraint::Check(expr) => write!(f, "CHECK({})", expr),
388            TableConstraint::PrimaryKey(cols) => {
389                let col_names: Vec<&str> = cols.iter().map(|c| c.value.as_str()).collect();
390                write!(f, "PRIMARY KEY({})", col_names.join(", "))
391            }
392            TableConstraint::ForeignKey(fk) => {
393                write!(f, "FOREIGN KEY({}) REFERENCES {}", fk.column, fk.ref_table)?;
394                if let Some(ref col) = fk.ref_column {
395                    write!(f, "({})", col)?;
396                }
397                if fk.on_delete != ForeignKeyAction::Restrict {
398                    write!(f, " ON DELETE {}", fk.on_delete)?;
399                }
400                if fk.on_update != ForeignKeyAction::Restrict {
401                    write!(f, " ON UPDATE {}", fk.on_update)?;
402                }
403                Ok(())
404            }
405        }
406    }
407}
408
409/// Helper enum for parsing - either a column definition or a table constraint
410#[derive(Debug, Clone, PartialEq)]
411pub enum ColumnOrConstraint {
412    Column(ColumnDefinition),
413    Constraint(TableConstraint),
414}
415
416/// DROP TABLE statement
417#[derive(Debug, Clone, PartialEq)]
418pub struct DropTableStatement {
419    pub token: Token,
420    pub table_name: Identifier,
421    pub if_exists: bool,
422}
423
424impl fmt::Display for DropTableStatement {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        let mut result = String::from("DROP TABLE ");
427        if self.if_exists {
428            result.push_str("IF EXISTS ");
429        }
430        result.push_str(&self.table_name.to_string());
431        write!(f, "{}", result)
432    }
433}
434
435/// TRUNCATE TABLE statement
436#[derive(Debug, Clone, PartialEq)]
437pub struct TruncateStatement {
438    pub token: Token,
439    pub table_name: Identifier,
440}
441
442impl fmt::Display for TruncateStatement {
443    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
444        write!(f, "TRUNCATE TABLE {}", self.table_name)
445    }
446}
447
448/// VACUUM statement — triggers manual cleanup of deleted rows and index compaction
449#[derive(Debug, Clone, PartialEq)]
450pub struct VacuumStatement {
451    pub token: Token,
452    pub table_name: Option<Identifier>,
453}
454
455impl fmt::Display for VacuumStatement {
456    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457        if let Some(ref table_name) = self.table_name {
458            write!(f, "VACUUM {}", table_name)
459        } else {
460            write!(f, "VACUUM")
461        }
462    }
463}
464
465/// Copy format for COPY FROM
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467pub enum CopyFormat {
468    Csv,
469    Json,
470}
471
472impl fmt::Display for CopyFormat {
473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474        match self {
475            CopyFormat::Csv => write!(f, "CSV"),
476            CopyFormat::Json => write!(f, "JSON"),
477        }
478    }
479}
480
481/// COPY table [(columns)] FROM 'file_path' [WITH (options)]
482#[derive(Debug, Clone, PartialEq)]
483pub struct CopyStatement {
484    pub token: Token,
485    pub table_name: Identifier,
486    pub columns: Vec<Identifier>,
487    pub file_path: String,
488    pub format: CopyFormat,
489    pub header: bool,
490    pub delimiter: u8,
491    pub null_string: Option<String>,
492}
493
494impl fmt::Display for CopyStatement {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        write!(f, "COPY {}", self.table_name)?;
497        if !self.columns.is_empty() {
498            write!(f, " (")?;
499            for (i, col) in self.columns.iter().enumerate() {
500                if i > 0 {
501                    write!(f, ", ")?;
502                }
503                write!(f, "{}", col)?;
504            }
505            write!(f, ")")?;
506        }
507        write!(f, " FROM '{}'", escape_sql_string(&self.file_path))?;
508        write!(f, " WITH (FORMAT {}", self.format)?;
509        if self.format == CopyFormat::Csv {
510            if self.header {
511                write!(f, ", HEADER true")?;
512            }
513            if self.delimiter != b',' {
514                write!(
515                    f,
516                    ", DELIMITER '{}'",
517                    escape_sql_string(&(self.delimiter as char).to_string())
518                )?;
519            }
520        }
521        if let Some(ref ns) = self.null_string {
522            write!(f, ", NULL '{}'", escape_sql_string(ns))?;
523        }
524        write!(f, ")")
525    }
526}
527
528/// ALTER TABLE operation type
529#[derive(Debug, Clone, Copy, PartialEq, Eq)]
530pub enum AlterTableOperation {
531    AddColumn,
532    AddConstraint,
533    DropColumn,
534    DropConstraint,
535    RenameColumn,
536    ModifyColumn,
537    RenameTable,
538}
539
540/// ALTER TABLE statement
541#[derive(Debug, Clone, PartialEq)]
542pub struct AlterTableStatement {
543    pub token: Token,
544    pub table_name: Identifier,
545    pub operation: AlterTableOperation,
546    pub column_def: Option<ColumnDefinition>,
547    pub table_constraint: Option<TableConstraint>,
548    pub column_name: Option<Identifier>,
549    pub constraint_name: Option<Identifier>,
550    pub if_exists: bool,
551    pub new_column_name: Option<Identifier>,
552    pub new_table_name: Option<Identifier>,
553}
554
555impl fmt::Display for AlterTableStatement {
556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557        let mut result = format!("ALTER TABLE {} ", self.table_name);
558        match self.operation {
559            AlterTableOperation::AddColumn => {
560                if let Some(ref col) = self.column_def {
561                    result.push_str(&format!("ADD COLUMN {}", col));
562                }
563            }
564            AlterTableOperation::AddConstraint => {
565                if let Some(ref constraint) = self.table_constraint {
566                    result.push_str(&format!("ADD CONSTRAINT {}", constraint));
567                }
568            }
569            AlterTableOperation::DropColumn => {
570                if let Some(ref name) = self.column_name {
571                    result.push_str(&format!("DROP COLUMN {}", name));
572                }
573            }
574            AlterTableOperation::DropConstraint => {
575                result.push_str("DROP CONSTRAINT ");
576                if self.if_exists {
577                    result.push_str("IF EXISTS ");
578                }
579                if let Some(ref name) = self.constraint_name {
580                    result.push_str(&name.to_string());
581                }
582            }
583            AlterTableOperation::RenameColumn => {
584                if let (Some(ref old), Some(ref new)) = (&self.column_name, &self.new_column_name) {
585                    result.push_str(&format!("RENAME COLUMN {} TO {}", old, new));
586                }
587            }
588            AlterTableOperation::ModifyColumn => {
589                if let Some(ref col) = self.column_def {
590                    result.push_str(&format!("MODIFY COLUMN {}", col));
591                }
592            }
593            AlterTableOperation::RenameTable => {
594                if let Some(ref name) = self.new_table_name {
595                    result.push_str(&format!("RENAME TO {}", name));
596                }
597            }
598        }
599        write!(f, "{}", result)
600    }
601}
602
603/// ALTER INDEX statement
604#[derive(Debug, Clone, PartialEq)]
605pub struct AlterIndexStatement {
606    pub token: Token,
607    pub index_name: Identifier,
608    pub new_index_name: Identifier,
609}
610
611impl fmt::Display for AlterIndexStatement {
612    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613        write!(
614            f,
615            "ALTER INDEX {} RENAME TO {}",
616            self.index_name, self.new_index_name
617        )
618    }
619}
620
621/// Index type for USING clause
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623pub enum IndexMethod {
624    /// B-tree index (default for INTEGER, FLOAT, TIMESTAMP) - good for range queries
625    BTree,
626    /// Hash index (default for TEXT, JSON) - good for equality lookups
627    Hash,
628    /// Bitmap index (default for BOOLEAN) - good for low-cardinality columns
629    Bitmap,
630    /// HNSW index - approximate nearest neighbor search for vector columns
631    Hnsw,
632}
633
634impl fmt::Display for IndexMethod {
635    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
636        match self {
637            IndexMethod::BTree => write!(f, "BTREE"),
638            IndexMethod::Hash => write!(f, "HASH"),
639            IndexMethod::Bitmap => write!(f, "BITMAP"),
640            IndexMethod::Hnsw => write!(f, "HNSW"),
641        }
642    }
643}
644
645/// CREATE INDEX statement
646#[derive(Debug, Clone, PartialEq)]
647pub struct CreateIndexStatement {
648    pub token: Token,
649    pub index_name: Identifier,
650    pub table_name: Identifier,
651    pub columns: Vec<Identifier>,
652    pub is_unique: bool,
653    pub if_not_exists: bool,
654    /// Optional index type from USING clause (None = auto-select based on column type)
655    pub index_method: Option<IndexMethod>,
656    /// Optional WITH clause for index parameters (e.g., HNSW m, ef_construction, ef_search, metric)
657    pub options: Vec<(String, Expression)>,
658    /// Optional partial-index predicate from WHERE clause
659    pub where_clause: Option<Box<Expression>>,
660    /// PostgreSQL-style operator class following the sole key column.
661    pub operator_class: Option<ObjectName>,
662}
663
664impl fmt::Display for CreateIndexStatement {
665    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666        let mut result = String::from("CREATE ");
667        if self.is_unique {
668            result.push_str("UNIQUE ");
669        }
670        result.push_str("INDEX ");
671        if self.if_not_exists {
672            result.push_str("IF NOT EXISTS ");
673        }
674        result.push_str(&format!("{} ON {} (", self.index_name, self.table_name));
675        let mut cols: Vec<String> = self.columns.iter().map(|c| c.to_string()).collect();
676        if let (Some(first), Some(operator_class)) = (cols.first_mut(), &self.operator_class) {
677            first.push(' ');
678            first.push_str(&operator_class.to_string());
679        }
680        result.push_str(&cols.join(", "));
681        result.push(')');
682        if let Some(method) = &self.index_method {
683            result.push_str(&format!(" USING {}", method));
684        }
685        if !self.options.is_empty() {
686            result.push_str(" WITH (");
687            for (i, (key, value)) in self.options.iter().enumerate() {
688                if i > 0 {
689                    result.push_str(", ");
690                }
691                result.push_str(&format!("{} = {}", key, value));
692            }
693            result.push(')');
694        }
695        if let Some(ref where_clause) = self.where_clause {
696            result.push_str(&format!(" WHERE {}", where_clause));
697        }
698        write!(f, "{}", result)
699    }
700}
701
702/// DROP INDEX statement
703#[derive(Debug, Clone, PartialEq)]
704pub struct DropIndexStatement {
705    pub token: Token,
706    pub index_name: Identifier,
707    pub table_name: Option<Identifier>,
708    pub if_exists: bool,
709}
710
711impl fmt::Display for DropIndexStatement {
712    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
713        let mut result = String::from("DROP INDEX ");
714        if self.if_exists {
715            result.push_str("IF EXISTS ");
716        }
717        result.push_str(&self.index_name.to_string());
718        if let Some(ref table) = self.table_name {
719            result.push_str(&format!(" ON {}", table));
720        }
721        write!(f, "{}", result)
722    }
723}
724
725/// CREATE VIEW statement
726#[derive(Debug, Clone, PartialEq)]
727pub struct CreateViewStatement {
728    pub token: Token,
729    pub view_name: Identifier,
730    pub query: Box<SelectStatement>,
731    pub if_not_exists: bool,
732}
733
734impl fmt::Display for CreateViewStatement {
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        let mut result = String::from("CREATE VIEW ");
737        if self.if_not_exists {
738            result.push_str("IF NOT EXISTS ");
739        }
740        result.push_str(&format!("{} AS {}", self.view_name, self.query));
741        write!(f, "{}", result)
742    }
743}
744
745/// DROP VIEW statement
746#[derive(Debug, Clone, PartialEq)]
747pub struct DropViewStatement {
748    pub token: Token,
749    pub view_name: Identifier,
750    pub if_exists: bool,
751}
752
753impl fmt::Display for DropViewStatement {
754    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755        let mut result = String::from("DROP VIEW ");
756        if self.if_exists {
757            result.push_str("IF EXISTS ");
758        }
759        result.push_str(&self.view_name.to_string());
760        write!(f, "{}", result)
761    }
762}