sql_ast/ast/
mod.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.
12
13//! SQL Abstract Syntax Tree (AST) types
14
15mod data_type;
16mod ddl;
17mod operator;
18mod query;
19mod value;
20
21use std::fmt;
22
23pub use self::data_type::DataType;
24pub use self::ddl::{
25    AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, TableConstraint,
26};
27pub use self::operator::{BinaryOperator, UnaryOperator};
28pub use self::query::{
29    Cte, Fetch, Join, JoinConstraint, JoinOperator, OrderByExpr, Query, Select, SelectItem,
30    SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Values,
31};
32pub use self::value::{DateTimeField, Value};
33
34struct DisplaySeparated<'a, T>
35where
36    T: fmt::Display,
37{
38    slice: &'a [T],
39    sep: &'static str,
40}
41
42impl<'a, T> fmt::Display for DisplaySeparated<'a, T>
43where
44    T: fmt::Display,
45{
46    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47        let mut delim = "";
48        for t in self.slice {
49            write!(f, "{}", delim)?;
50            delim = self.sep;
51            write!(f, "{}", t)?;
52        }
53        Ok(())
54    }
55}
56
57fn display_separated<'a, T>(slice: &'a [T], sep: &'static str) -> DisplaySeparated<'a, T>
58where
59    T: fmt::Display,
60{
61    DisplaySeparated { slice, sep }
62}
63
64fn display_comma_separated<T>(slice: &[T]) -> DisplaySeparated<'_, T>
65where
66    T: fmt::Display,
67{
68    DisplaySeparated { slice, sep: ", " }
69}
70
71/// An identifier, decomposed into its value or character data and the quote style.
72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73pub struct Ident {
74    /// The value of the identifier without quotes.
75    pub value: String,
76    /// The starting quote if any. Valid quote characters are the single quote,
77    /// double quote, backtick, and opening square bracket.
78    pub quote_style: Option<char>,
79}
80
81impl Ident {
82    /// Create a new identifier with the given value and no quotes.
83    pub fn new<S>(value: S) -> Self
84    where
85        S: Into<String>,
86    {
87        Ident {
88            value: value.into(),
89            quote_style: None,
90        }
91    }
92
93    /// Create a new quoted identifier with the given quote and value. This function
94    /// panics if the given quote is not a valid quote character.
95    pub fn with_quote<S>(quote: char, value: S) -> Self
96    where
97        S: Into<String>,
98    {
99        assert!(quote == '\'' || quote == '"' || quote == '`' || quote == '[');
100        Ident {
101            value: value.into(),
102            quote_style: Some(quote),
103        }
104    }
105}
106
107impl From<&str> for Ident {
108    fn from(value: &str) -> Self {
109        Ident {
110            value: value.to_string(),
111            quote_style: None,
112        }
113    }
114}
115
116impl fmt::Display for Ident {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        match self.quote_style {
119            Some(q) if q == '"' || q == '\'' || q == '`' => write!(f, "{}{}{}", q, self.value, q),
120            Some('[') => write!(f, "[{}]", self.value),
121            None => f.write_str(&self.value),
122            _ => panic!("unexpected quote style"),
123        }
124    }
125}
126
127/// A name of a table, view, custom type, etc., possibly multi-part, i.e. db.schema.obj
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub struct ObjectName(pub Vec<Ident>);
130
131impl fmt::Display for ObjectName {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        write!(f, "{}", display_separated(&self.0, "."))
134    }
135}
136
137/// An SQL expression of any type.
138///
139/// The parser does not distinguish between expressions of different types
140/// (e.g. boolean vs string), so the caller must handle expressions of
141/// inappropriate type, like `WHERE 1` or `SELECT 1=1`, as necessary.
142#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143pub enum Expr {
144    /// Identifier e.g. table name or column name
145    Identifier(Ident),
146    /// Unqualified wildcard (`*`). SQL allows this in limited contexts, such as:
147    /// - right after `SELECT` (which is represented as a [SelectItem::Wildcard] instead)
148    /// - or as part of an aggregate function, e.g. `COUNT(*)`,
149    ///
150    /// ...but we currently also accept it in contexts where it doesn't make
151    /// sense, such as `* + *`
152    Wildcard,
153    /// Qualified wildcard, e.g. `alias.*` or `schema.table.*`.
154    /// (Same caveats apply to `QualifiedWildcard` as to `Wildcard`.)
155    QualifiedWildcard(Vec<Ident>),
156    /// Multi-part identifier, e.g. `table_alias.column` or `schema.table.col`
157    CompoundIdentifier(Vec<Ident>),
158    /// `IS NULL` expression
159    IsNull(Box<Expr>),
160    /// `IS NOT NULL` expression
161    IsNotNull(Box<Expr>),
162    /// `[ NOT ] IN (val1, val2, ...)`
163    InList {
164        expr: Box<Expr>,
165        list: Vec<Expr>,
166        negated: bool,
167    },
168    ValueList(Vec<Expr>),
169    /// `[ NOT ] IN (SELECT ...)`
170    InSubquery {
171        expr: Box<Expr>,
172        subquery: Box<Query>,
173        negated: bool,
174    },
175    /// `<expr> [ NOT ] BETWEEN <low> AND <high>`
176    Between {
177        expr: Box<Expr>,
178        negated: bool,
179        low: Box<Expr>,
180        high: Box<Expr>,
181    },
182    /// Binary operation e.g. `1 + 1` or `foo > bar`
183    BinaryOp {
184        left: Box<Expr>,
185        op: BinaryOperator,
186        right: Box<Expr>,
187    },
188    /// Unary operation e.g. `NOT foo`
189    UnaryOp {
190        op: UnaryOperator,
191        expr: Box<Expr>,
192    },
193    /// CAST an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
194    Cast {
195        expr: Box<Expr>,
196        data_type: DataType,
197    },
198    Extract {
199        field: DateTimeField,
200        expr: Box<Expr>,
201    },
202    /// `expr COLLATE collation`
203    Collate {
204        expr: Box<Expr>,
205        collation: ObjectName,
206    },
207    /// Nested expression e.g. `(foo > bar)` or `(1)`
208    Nested(Box<Expr>),
209    /// A literal value, such as string, number, date or NULL
210    Value(Value),
211    /// Scalar function call e.g. `LEFT(foo, 5)`
212    Function(Function),
213    /// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
214    ///
215    /// Note we only recognize a complete single expression as `<condition>`,
216    /// not `< 0` nor `1, 2, 3` as allowed in a `<simple when clause>` per
217    /// <https://jakewheat.github.io/sql-overview/sql-2011-foundation-grammar.html#simple-when-clause>
218    Case {
219        operand: Option<Box<Expr>>,
220        conditions: Vec<Expr>,
221        results: Vec<Expr>,
222        else_result: Option<Box<Expr>>,
223    },
224    /// An exists expression `EXISTS(SELECT ...)`, used in expressions like
225    /// `WHERE EXISTS (SELECT ...)`.
226    Exists(Box<Query>),
227    /// A parenthesized subquery `(SELECT ...)`, used in expression like
228    /// `SELECT (subquery) AS x` or `WHERE (subquery) = x`
229    Subquery(Box<Query>),
230}
231
232impl fmt::Display for Expr {
233    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234        match self {
235            Expr::Identifier(s) => write!(f, "{}", s),
236            Expr::Wildcard => f.write_str("*"),
237            Expr::QualifiedWildcard(q) => write!(f, "{}.*", display_separated(q, ".")),
238            Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")),
239            Expr::IsNull(ast) => write!(f, "{} IS NULL", ast),
240            Expr::IsNotNull(ast) => write!(f, "{} IS NOT NULL", ast),
241            Expr::InList {
242                expr,
243                list,
244                negated,
245            } => write!(
246                f,
247                "{} {}IN ({})",
248                expr,
249                if *negated { "NOT " } else { "" },
250                display_comma_separated(list)
251            ),
252            Expr::ValueList(list) => write!(f, "({})", display_comma_separated(list)),
253            Expr::InSubquery {
254                expr,
255                subquery,
256                negated,
257            } => write!(
258                f,
259                "{} {}IN ({})",
260                expr,
261                if *negated { "NOT " } else { "" },
262                subquery
263            ),
264            Expr::Between {
265                expr,
266                negated,
267                low,
268                high,
269            } => write!(
270                f,
271                "{} {}BETWEEN {} AND {}",
272                expr,
273                if *negated { "NOT " } else { "" },
274                low,
275                high
276            ),
277            Expr::BinaryOp { left, op, right } => write!(f, "{} {} {}", left, op, right),
278            Expr::UnaryOp { op, expr } => write!(f, "{} {}", op, expr),
279            Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
280            Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
281            Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
282            Expr::Nested(ast) => write!(f, "({})", ast),
283            Expr::Value(v) => write!(f, "{}", v),
284            Expr::Function(fun) => write!(f, "{}", fun),
285            Expr::Case {
286                operand,
287                conditions,
288                results,
289                else_result,
290            } => {
291                f.write_str("CASE")?;
292                if let Some(operand) = operand {
293                    write!(f, " {}", operand)?;
294                }
295                for (c, r) in conditions.iter().zip(results) {
296                    write!(f, " WHEN {} THEN {}", c, r)?;
297                }
298
299                if let Some(else_result) = else_result {
300                    write!(f, " ELSE {}", else_result)?;
301                }
302                f.write_str(" END")
303            }
304            Expr::Exists(s) => write!(f, "EXISTS ({})", s),
305            Expr::Subquery(s) => write!(f, "({})", s),
306        }
307    }
308}
309
310/// A window specification (i.e. `OVER (PARTITION BY .. ORDER BY .. etc.)`)
311#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub struct WindowSpec {
313    pub partition_by: Vec<Expr>,
314    pub order_by: Vec<OrderByExpr>,
315    pub window_frame: Option<WindowFrame>,
316}
317
318impl fmt::Display for WindowSpec {
319    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320        let mut delim = "";
321        if !self.partition_by.is_empty() {
322            delim = " ";
323            write!(
324                f,
325                "PARTITION BY {}",
326                display_comma_separated(&self.partition_by)
327            )?;
328        }
329        if !self.order_by.is_empty() {
330            f.write_str(delim)?;
331            delim = " ";
332            write!(f, "ORDER BY {}", display_comma_separated(&self.order_by))?;
333        }
334        if let Some(window_frame) = &self.window_frame {
335            if let Some(end_bound) = &window_frame.end_bound {
336                f.write_str(delim)?;
337                write!(
338                    f,
339                    "{} BETWEEN {} AND {}",
340                    window_frame.units, window_frame.start_bound, end_bound
341                )?;
342            } else {
343                f.write_str(delim)?;
344                write!(f, "{} {}", window_frame.units, window_frame.start_bound)?;
345            }
346        }
347        Ok(())
348    }
349}
350
351/// Specifies the data processed by a window function, e.g.
352/// `RANGE UNBOUNDED PRECEDING` or `ROWS BETWEEN 5 PRECEDING AND CURRENT ROW`.
353///
354/// Note: The parser does not validate the specified bounds; the caller should
355/// reject invalid bounds like `ROWS UNBOUNDED FOLLOWING` before execution.
356#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub struct WindowFrame {
358    pub units: WindowFrameUnits,
359    pub start_bound: WindowFrameBound,
360    /// The right bound of the `BETWEEN .. AND` clause. The end bound of `None`
361    /// indicates the shorthand form (e.g. `ROWS 1 PRECEDING`), which must
362    /// behave the same as `end_bound = WindowFrameBound::CurrentRow`.
363    pub end_bound: Option<WindowFrameBound>,
364    // TBD: EXCLUDE
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, Hash)]
368pub enum WindowFrameUnits {
369    Rows,
370    Range,
371    Groups,
372}
373
374impl fmt::Display for WindowFrameUnits {
375    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
376        f.write_str(match self {
377            WindowFrameUnits::Rows => "ROWS",
378            WindowFrameUnits::Range => "RANGE",
379            WindowFrameUnits::Groups => "GROUPS",
380        })
381    }
382}
383
384impl FromStr for WindowFrameUnits {
385    type Err = ParserError;
386
387    fn from_str(s: &str) -> Result<Self, Self::Err> {
388        match s {
389            "ROWS" => Ok(WindowFrameUnits::Rows),
390            "RANGE" => Ok(WindowFrameUnits::Range),
391            "GROUPS" => Ok(WindowFrameUnits::Groups),
392            _ => Err(ParserError::ParserError(format!(
393                "Expected ROWS, RANGE, or GROUPS, found: {}",
394                s
395            ))),
396        }
397    }
398}
399
400/// Specifies [WindowFrame]'s `start_bound` and `end_bound`
401#[derive(Debug, Clone, PartialEq, Eq, Hash)]
402pub enum WindowFrameBound {
403    /// `CURRENT ROW`
404    CurrentRow,
405    /// `<N> PRECEDING` or `UNBOUNDED PRECEDING`
406    Preceding(Option<u64>),
407    /// `<N> FOLLOWING` or `UNBOUNDED FOLLOWING`.
408    Following(Option<u64>),
409}
410
411impl fmt::Display for WindowFrameBound {
412    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
413        match self {
414            WindowFrameBound::CurrentRow => f.write_str("CURRENT ROW"),
415            WindowFrameBound::Preceding(None) => f.write_str("UNBOUNDED PRECEDING"),
416            WindowFrameBound::Following(None) => f.write_str("UNBOUNDED FOLLOWING"),
417            WindowFrameBound::Preceding(Some(n)) => write!(f, "{} PRECEDING", n),
418            WindowFrameBound::Following(Some(n)) => write!(f, "{} FOLLOWING", n),
419        }
420    }
421}
422
423/// A top-level statement (SELECT, INSERT, CREATE, etc.)
424#[allow(clippy::large_enum_variant)]
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
426pub enum Statement {
427    /// SELECT
428    Query(Box<Query>),
429    /// INSERT
430    Insert {
431        /// TABLE
432        table_name: ObjectName,
433        /// COLUMNS
434        columns: Vec<Ident>,
435        /// A SQL query that specifies what to insert
436        source: Box<Query>,
437    },
438    Copy {
439        /// TABLE
440        table_name: ObjectName,
441        /// COLUMNS
442        columns: Vec<Ident>,
443        /// VALUES a vector of values to be copied
444        values: Vec<Option<String>>,
445    },
446    /// UPDATE
447    Update {
448        /// TABLE
449        table_name: ObjectName,
450        /// Column assignments
451        assignments: Vec<Assignment>,
452        /// WHERE
453        selection: Option<Expr>,
454    },
455    /// DELETE
456    Delete {
457        /// FROM
458        table_name: ObjectName,
459        /// WHERE
460        selection: Option<Expr>,
461    },
462    /// CREATE VIEW
463    CreateView {
464        /// View name
465        name: ObjectName,
466        columns: Vec<Ident>,
467        query: Box<Query>,
468        materialized: bool,
469        with_options: Vec<SqlOption>,
470    },
471    /// CREATE TABLE
472    CreateTable {
473        if_not_exists: bool,
474        /// Table name
475        name: ObjectName,
476        /// Optional schema
477        columns: Vec<ColumnDef>,
478        constraints: Vec<TableConstraint>,
479        with_options: Vec<SqlOption>,
480        external: bool,
481        file_format: Option<FileFormat>,
482        location: Option<String>,
483    },
484    /// ALTER TABLE
485    AlterTable {
486        /// Table name
487        name: ObjectName,
488        operation: AlterTableOperation,
489    },
490    /// DROP
491    Drop {
492        /// The type of the object to drop: TABLE, VIEW, etc.
493        object_type: ObjectType,
494        /// An optional `IF EXISTS` clause. (Non-standard.)
495        if_exists: bool,
496        /// One or more objects to drop. (ANSI SQL requires exactly one.)
497        names: Vec<ObjectName>,
498        /// Whether `CASCADE` was specified. This will be `false` when
499        /// `RESTRICT` or no drop behavior at all was specified.
500        cascade: bool,
501    },
502}
503
504impl fmt::Display for Statement {
505    // Clippy thinks this function is too complicated, but it is painful to
506    // split up without extracting structs for each `Statement` variant.
507    #[allow(clippy::cognitive_complexity)]
508    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
509        match self {
510            Statement::Query(s) => write!(f, "{}", s),
511            Statement::Insert {
512                table_name,
513                columns,
514                source,
515            } => {
516                write!(f, "INSERT INTO {} ", table_name)?;
517                if !columns.is_empty() {
518                    write!(f, "({}) ", display_comma_separated(columns))?;
519                }
520                write!(f, "{}", source)
521            }
522            Statement::Copy {
523                table_name,
524                columns,
525                values,
526            } => {
527                write!(f, "COPY {}", table_name)?;
528                if !columns.is_empty() {
529                    write!(f, " ({})", display_comma_separated(columns))?;
530                }
531                write!(f, " FROM stdin; ")?;
532                if !values.is_empty() {
533                    writeln!(f)?;
534                    let mut delim = "";
535                    for v in values {
536                        write!(f, "{}", delim)?;
537                        delim = "\t";
538                        if let Some(v) = v {
539                            write!(f, "{}", v)?;
540                        } else {
541                            write!(f, "\\N")?;
542                        }
543                    }
544                }
545                write!(f, "\n\\.")
546            }
547            Statement::Update {
548                table_name,
549                assignments,
550                selection,
551            } => {
552                write!(f, "UPDATE {}", table_name)?;
553                if !assignments.is_empty() {
554                    write!(f, " SET ")?;
555                    write!(f, "{}", display_comma_separated(assignments))?;
556                }
557                if let Some(selection) = selection {
558                    write!(f, " WHERE {}", selection)?;
559                }
560                Ok(())
561            }
562            Statement::Delete {
563                table_name,
564                selection,
565            } => {
566                write!(f, "DELETE FROM {}", table_name)?;
567                if let Some(selection) = selection {
568                    write!(f, " WHERE {}", selection)?;
569                }
570                Ok(())
571            }
572            Statement::CreateView {
573                name,
574                columns,
575                query,
576                materialized,
577                with_options,
578            } => {
579                write!(f, "CREATE")?;
580                if *materialized {
581                    write!(f, " MATERIALIZED")?;
582                }
583
584                write!(f, " VIEW {}", name)?;
585
586                if !with_options.is_empty() {
587                    write!(f, " WITH ({})", display_comma_separated(with_options))?;
588                }
589
590                if !columns.is_empty() {
591                    write!(f, " ({})", display_comma_separated(columns))?;
592                }
593
594                write!(f, " AS {}", query)
595            }
596            Statement::CreateTable {
597                name,
598                if_not_exists,
599                columns,
600                constraints,
601                with_options,
602                external,
603                file_format,
604                location,
605            } => {
606                write!(
607                    f,
608                    "CREATE {}TABLE {}{} ({}",
609                    if *external { "EXTERNAL " } else { "" },
610                    if *if_not_exists { "IF NOT EXISTS " } else { "" },
611                    name,
612                    display_comma_separated(columns)
613                )?;
614                if !constraints.is_empty() {
615                    write!(f, ", {}", display_comma_separated(constraints))?;
616                }
617                write!(f, ")")?;
618
619                if *external {
620                    write!(
621                        f,
622                        " STORED AS {} LOCATION '{}'",
623                        file_format.as_ref().unwrap(),
624                        location.as_ref().unwrap()
625                    )?;
626                }
627                if !with_options.is_empty() {
628                    write!(f, " WITH ({})", display_comma_separated(with_options))?;
629                }
630                Ok(())
631            }
632            Statement::AlterTable { name, operation } => {
633                write!(f, "ALTER TABLE {} {}", name, operation)
634            }
635            Statement::Drop {
636                object_type,
637                if_exists,
638                names,
639                cascade,
640            } => write!(
641                f,
642                "DROP {}{} {}{}",
643                object_type,
644                if *if_exists { " IF EXISTS" } else { "" },
645                display_comma_separated(names),
646                if *cascade { " CASCADE" } else { "" },
647            ),
648        }
649    }
650}
651
652/// SQL assignment `foo = expr` as used in SQLUpdate
653#[derive(Debug, Clone, PartialEq, Eq, Hash)]
654pub struct Assignment {
655    pub id: Ident,
656    pub value: Expr,
657}
658
659impl fmt::Display for Assignment {
660    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
661        write!(f, "{} = {}", self.id, self.value)
662    }
663}
664
665/// A function call
666#[derive(Debug, Clone, PartialEq, Eq, Hash)]
667pub struct Function {
668    pub name: ObjectName,
669    pub args: Vec<Expr>,
670    pub over: Option<WindowSpec>,
671    // aggregate functions may specify eg `COUNT(DISTINCT x)`
672    pub distinct: bool,
673}
674
675impl fmt::Display for Function {
676    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
677        write!(
678            f,
679            "{}({}{})",
680            self.name,
681            if self.distinct { "DISTINCT " } else { "" },
682            display_comma_separated(&self.args),
683        )?;
684        if let Some(o) = &self.over {
685            write!(f, " OVER ({})", o)?;
686        }
687        Ok(())
688    }
689}
690
691/// External table's available file format
692#[derive(Debug, Clone, PartialEq, Eq, Hash)]
693pub enum FileFormat {
694    TEXTFILE,
695    SEQUENCEFILE,
696    ORC,
697    PARQUET,
698    AVRO,
699    RCFILE,
700    JSONFILE,
701}
702
703impl fmt::Display for FileFormat {
704    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
705        use self::FileFormat::*;
706        f.write_str(match self {
707            TEXTFILE => "TEXTFILE",
708            SEQUENCEFILE => "SEQUENCEFILE",
709            ORC => "ORC",
710            PARQUET => "PARQUET",
711            AVRO => "AVRO",
712            RCFILE => "RCFILE",
713            JSONFILE => "TEXTFILE",
714        })
715    }
716}
717
718use crate::parser::ParserError;
719use std::str::FromStr;
720impl FromStr for FileFormat {
721    type Err = ParserError;
722
723    fn from_str(s: &str) -> Result<Self, Self::Err> {
724        use self::FileFormat::*;
725        match s {
726            "TEXTFILE" => Ok(TEXTFILE),
727            "SEQUENCEFILE" => Ok(SEQUENCEFILE),
728            "ORC" => Ok(ORC),
729            "PARQUET" => Ok(PARQUET),
730            "AVRO" => Ok(AVRO),
731            "RCFILE" => Ok(RCFILE),
732            "JSONFILE" => Ok(JSONFILE),
733            _ => Err(ParserError::ParserError(format!(
734                "Unexpected file format: {}",
735                s
736            ))),
737        }
738    }
739}
740
741#[derive(Debug, Clone, PartialEq, Eq, Hash)]
742pub enum ObjectType {
743    Table,
744    View,
745}
746
747impl fmt::Display for ObjectType {
748    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
749        f.write_str(match self {
750            ObjectType::Table => "TABLE",
751            ObjectType::View => "VIEW",
752        })
753    }
754}
755
756#[derive(Debug, Clone, PartialEq, Eq, Hash)]
757pub struct SqlOption {
758    pub name: Ident,
759    pub value: Value,
760}
761
762impl fmt::Display for SqlOption {
763    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
764        write!(f, "{} = {}", self.name, self.value)
765    }
766}