iffyio commented on code in PR #2250:
URL: 
https://github.com/apache/datafusion-sqlparser-rs/pull/2250#discussion_r3136372903


##########
src/ast/ddl.rs:
##########
@@ -5430,6 +5430,151 @@ impl Spanned for AlterFunction {
     }
 }
 
+/// PostgreSQL text search object kind.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TextSearchObjectType {
+    /// `DICTIONARY`
+    Dictionary,
+    /// `CONFIGURATION`
+    Configuration,
+    /// `TEMPLATE`
+    Template,
+    /// `PARSER`
+    Parser,
+}
+
+impl fmt::Display for TextSearchObjectType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            TextSearchObjectType::Dictionary => write!(f, "DICTIONARY"),
+            TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
+            TextSearchObjectType::Template => write!(f, "TEMPLATE"),
+            TextSearchObjectType::Parser => write!(f, "PARSER"),
+        }
+    }
+}
+
+/// PostgreSQL `CREATE TEXT SEARCH ...` statement.

Review Comment:
   ```suggestion
   /// `CREATE TEXT SEARCH ...` statement.
   ```
   same comment regarding doc link



##########
src/ast/ddl.rs:
##########
@@ -5430,6 +5430,151 @@ impl Spanned for AlterFunction {
     }
 }
 
+/// PostgreSQL text search object kind.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TextSearchObjectType {
+    /// `DICTIONARY`
+    Dictionary,
+    /// `CONFIGURATION`
+    Configuration,
+    /// `TEMPLATE`
+    Template,
+    /// `PARSER`
+    Parser,
+}
+
+impl fmt::Display for TextSearchObjectType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            TextSearchObjectType::Dictionary => write!(f, "DICTIONARY"),
+            TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
+            TextSearchObjectType::Template => write!(f, "TEMPLATE"),
+            TextSearchObjectType::Parser => write!(f, "PARSER"),
+        }
+    }
+}
+
+/// PostgreSQL `CREATE TEXT SEARCH ...` statement.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateTextSearch {
+    /// The specific text search object type.
+    pub object_type: TextSearchObjectType,
+    /// Object name.
+    pub name: ObjectName,
+    /// Parenthesized options.
+    pub options: Vec<SqlOption>,
+}
+
+impl fmt::Display for CreateTextSearch {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "CREATE TEXT SEARCH {} {} ({})",
+            self.object_type,
+            self.name,
+            display_comma_separated(&self.options)
+        )
+    }
+}
+
+impl Spanned for CreateTextSearch {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+/// Option assignment used by `ALTER TEXT SEARCH DICTIONARY ... ( ... )`.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterTextSearchDictionaryOption {
+    /// Option name.
+    pub key: Ident,
+    /// Optional value (`option [= value]`).
+    pub value: Option<Expr>,
+}
+
+impl fmt::Display for AlterTextSearchDictionaryOption {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.value {
+            Some(value) => write!(f, "{} = {}", self.key, value),
+            None => write!(f, "{}", self.key),
+        }
+    }
+}
+
+/// Operation for PostgreSQL `ALTER TEXT SEARCH ...`.

Review Comment:
   ```suggestion
   /// Operation for `ALTER TEXT SEARCH ...`.
   ```
   same comment regarding doc link



##########
src/ast/ddl.rs:
##########
@@ -5430,6 +5430,151 @@ impl Spanned for AlterFunction {
     }
 }
 
+/// PostgreSQL text search object kind.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TextSearchObjectType {
+    /// `DICTIONARY`
+    Dictionary,
+    /// `CONFIGURATION`
+    Configuration,
+    /// `TEMPLATE`
+    Template,
+    /// `PARSER`
+    Parser,
+}
+
+impl fmt::Display for TextSearchObjectType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            TextSearchObjectType::Dictionary => write!(f, "DICTIONARY"),
+            TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
+            TextSearchObjectType::Template => write!(f, "TEMPLATE"),
+            TextSearchObjectType::Parser => write!(f, "PARSER"),
+        }
+    }
+}
+
+/// PostgreSQL `CREATE TEXT SEARCH ...` statement.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateTextSearch {
+    /// The specific text search object type.
+    pub object_type: TextSearchObjectType,
+    /// Object name.
+    pub name: ObjectName,
+    /// Parenthesized options.
+    pub options: Vec<SqlOption>,
+}
+
+impl fmt::Display for CreateTextSearch {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "CREATE TEXT SEARCH {} {} ({})",
+            self.object_type,
+            self.name,
+            display_comma_separated(&self.options)
+        )
+    }
+}
+
+impl Spanned for CreateTextSearch {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+/// Option assignment used by `ALTER TEXT SEARCH DICTIONARY ... ( ... )`.

Review Comment:
   Can we add a link to the docs for this struct?



##########
src/parser/mod.rs:
##########
@@ -5186,6 +5188,140 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_text_search_object_type(&mut self) -> 
Result<TextSearchObjectType, ParserError> {
+        match self.expect_one_of_keywords(&[
+            Keyword::DICTIONARY,
+            Keyword::CONFIGURATION,
+            Keyword::TEMPLATE,
+            Keyword::PARSER,
+        ])? {
+            Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
+            Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
+            Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
+            Keyword::PARSER => Ok(TextSearchObjectType::Parser),
+            // unreachable because expect_one_of_keywords used above

Review Comment:
   ```suggestion
   ```



##########
tests/sqlparser_postgres.rs:
##########
@@ -8279,6 +8279,37 @@ fn parse_alter_function_and_aggregate() {
         .is_err());
 }
 
+#[test]
+fn parse_create_and_alter_text_search() {

Review Comment:
   can we split this into `parse_create_text_search` and 
`parse_alter_text_search` functions? since they're different syntax



##########
src/ast/mod.rs:
##########
@@ -3779,6 +3785,11 @@ pub enum Statement {
     /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-alteropclass.html)
     AlterOperatorClass(AlterOperatorClass),
     /// ```sql
+    /// ALTER TEXT SEARCH { DICTIONARY | CONFIGURATION | TEMPLATE | PARSER }

Review Comment:
   ```suggestion
       /// A `ALTER TEXT SEARCH` statement
   ```



##########
src/ast/ddl.rs:
##########
@@ -5430,6 +5430,151 @@ impl Spanned for AlterFunction {
     }
 }
 
+/// PostgreSQL text search object kind.

Review Comment:
   ```suggestion
   /// Text search object kind.
   ```
   Can we instead add a [PostgreSQL] link to the reference docs. We can avoid 
mentioning a specific dialect as part of the main description (it goes stale 
quickly/immediately if there are other dialects that support the feature)



##########
src/ast/ddl.rs:
##########
@@ -5430,6 +5430,151 @@ impl Spanned for AlterFunction {
     }
 }
 
+/// PostgreSQL text search object kind.
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TextSearchObjectType {
+    /// `DICTIONARY`
+    Dictionary,
+    /// `CONFIGURATION`
+    Configuration,
+    /// `TEMPLATE`
+    Template,
+    /// `PARSER`
+    Parser,
+}
+
+impl fmt::Display for TextSearchObjectType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            TextSearchObjectType::Dictionary => write!(f, "DICTIONARY"),
+            TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
+            TextSearchObjectType::Template => write!(f, "TEMPLATE"),
+            TextSearchObjectType::Parser => write!(f, "PARSER"),
+        }
+    }
+}
+
+/// PostgreSQL `CREATE TEXT SEARCH ...` statement.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct CreateTextSearch {
+    /// The specific text search object type.
+    pub object_type: TextSearchObjectType,
+    /// Object name.
+    pub name: ObjectName,
+    /// Parenthesized options.
+    pub options: Vec<SqlOption>,
+}
+
+impl fmt::Display for CreateTextSearch {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "CREATE TEXT SEARCH {} {} ({})",
+            self.object_type,
+            self.name,
+            display_comma_separated(&self.options)
+        )
+    }
+}
+
+impl Spanned for CreateTextSearch {
+    fn span(&self) -> Span {
+        Span::empty()
+    }
+}
+
+/// Option assignment used by `ALTER TEXT SEARCH DICTIONARY ... ( ... )`.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct AlterTextSearchDictionaryOption {
+    /// Option name.
+    pub key: Ident,
+    /// Optional value (`option [= value]`).
+    pub value: Option<Expr>,
+}
+
+impl fmt::Display for AlterTextSearchDictionaryOption {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.value {
+            Some(value) => write!(f, "{} = {}", self.key, value),
+            None => write!(f, "{}", self.key),
+        }
+    }
+}
+
+/// Operation for PostgreSQL `ALTER TEXT SEARCH ...`.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum AlterTextSearchOperation {
+    /// `RENAME TO new_name`
+    RenameTo {
+        /// New name.
+        new_name: Ident,
+    },
+    /// `OWNER TO ...`
+    OwnerTo(Owner),
+    /// `SET SCHEMA schema_name`
+    SetSchema {
+        /// Target schema.
+        schema_name: ObjectName,
+    },
+    /// `( option [= value] [, ...] )`
+    SetOptions {
+        /// Dictionary options to apply.
+        options: Vec<AlterTextSearchDictionaryOption>,
+    },
+}
+
+impl fmt::Display for AlterTextSearchOperation {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            AlterTextSearchOperation::RenameTo { new_name } => write!(f, 
"RENAME TO {new_name}"),
+            AlterTextSearchOperation::OwnerTo(owner) => write!(f, "OWNER TO 
{owner}"),
+            AlterTextSearchOperation::SetSchema { schema_name } => {
+                write!(f, "SET SCHEMA {schema_name}")
+            }
+            AlterTextSearchOperation::SetOptions { options } => {
+                write!(f, "({})", display_comma_separated(options))
+            }
+        }
+    }
+}
+
+/// PostgreSQL `ALTER TEXT SEARCH ...` statement.

Review Comment:
   ```suggestion
   /// `ALTER TEXT SEARCH ...` statement.
   ```
   same comment regarding doc link



##########
src/ast/mod.rs:
##########
@@ -3720,6 +3721,11 @@ pub enum Statement {
     /// See 
[PostgreSQL](https://www.postgresql.org/docs/current/sql-createopclass.html)
     CreateOperatorClass(CreateOperatorClass),
     /// ```sql
+    /// CREATE TEXT SEARCH { DICTIONARY | CONFIGURATION | TEMPLATE | PARSER }

Review Comment:
   ```suggestion
       /// A `CREATE TEXT SEARCH` statement
   ```
   



##########
src/parser/mod.rs:
##########
@@ -5186,6 +5188,140 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_text_search_object_type(&mut self) -> 
Result<TextSearchObjectType, ParserError> {
+        match self.expect_one_of_keywords(&[
+            Keyword::DICTIONARY,
+            Keyword::CONFIGURATION,
+            Keyword::TEMPLATE,
+            Keyword::PARSER,
+        ])? {
+            Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
+            Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
+            Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
+            Keyword::PARSER => Ok(TextSearchObjectType::Parser),
+            // unreachable because expect_one_of_keywords used above
+            unexpected_keyword => Err(ParserError::ParserError(format!(
+                "Internal parser error: expected any of {{DICTIONARY, 
CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}"
+            ))),
+        }
+    }
+
+    /// Parse a PostgreSQL `CREATE TEXT SEARCH ...` statement.

Review Comment:
   ```suggestion
       /// Parse a `CREATE TEXT SEARCH ...` statement.
   ```



##########
src/parser/mod.rs:
##########
@@ -5186,6 +5188,140 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_text_search_object_type(&mut self) -> 
Result<TextSearchObjectType, ParserError> {
+        match self.expect_one_of_keywords(&[
+            Keyword::DICTIONARY,
+            Keyword::CONFIGURATION,
+            Keyword::TEMPLATE,
+            Keyword::PARSER,
+        ])? {
+            Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
+            Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
+            Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
+            Keyword::PARSER => Ok(TextSearchObjectType::Parser),
+            // unreachable because expect_one_of_keywords used above
+            unexpected_keyword => Err(ParserError::ParserError(format!(
+                "Internal parser error: expected any of {{DICTIONARY, 
CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}"
+            ))),
+        }
+    }
+
+    /// Parse a PostgreSQL `CREATE TEXT SEARCH ...` statement.
+    pub fn parse_create_text_search(&mut self) -> Result<CreateTextSearch, 
ParserError> {
+        self.expect_keywords(&[Keyword::TEXT, Keyword::SEARCH])?;
+        let object_type = self.parse_text_search_object_type()?;
+        let name = self.parse_object_name(false)?;
+        self.expect_token(&Token::LParen)?;
+        let options = self.parse_comma_separated(Parser::parse_sql_option)?;
+        self.expect_token(&Token::RParen)?;
+        Ok(CreateTextSearch {
+            object_type,
+            name,
+            options,
+        })
+    }
+
+    fn parse_alter_text_search_dictionary_option(
+        &mut self,
+    ) -> Result<AlterTextSearchDictionaryOption, ParserError> {
+        let key = self.parse_identifier()?;
+        let value = if self.consume_token(&Token::Eq) {
+            Some(self.parse_expr()?)
+        } else {
+            None
+        };
+        Ok(AlterTextSearchDictionaryOption { key, value })
+    }
+
+    /// Parse a PostgreSQL `ALTER TEXT SEARCH ...` statement.

Review Comment:
   ```suggestion
       /// Parse a `ALTER TEXT SEARCH ...` statement.
   ```



##########
src/parser/mod.rs:
##########
@@ -5186,6 +5188,140 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_text_search_object_type(&mut self) -> 
Result<TextSearchObjectType, ParserError> {
+        match self.expect_one_of_keywords(&[
+            Keyword::DICTIONARY,
+            Keyword::CONFIGURATION,
+            Keyword::TEMPLATE,
+            Keyword::PARSER,
+        ])? {
+            Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
+            Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
+            Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
+            Keyword::PARSER => Ok(TextSearchObjectType::Parser),
+            // unreachable because expect_one_of_keywords used above
+            unexpected_keyword => Err(ParserError::ParserError(format!(
+                "Internal parser error: expected any of {{DICTIONARY, 
CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}"
+            ))),
+        }
+    }
+
+    /// Parse a PostgreSQL `CREATE TEXT SEARCH ...` statement.
+    pub fn parse_create_text_search(&mut self) -> Result<CreateTextSearch, 
ParserError> {
+        self.expect_keywords(&[Keyword::TEXT, Keyword::SEARCH])?;
+        let object_type = self.parse_text_search_object_type()?;
+        let name = self.parse_object_name(false)?;
+        self.expect_token(&Token::LParen)?;
+        let options = self.parse_comma_separated(Parser::parse_sql_option)?;
+        self.expect_token(&Token::RParen)?;
+        Ok(CreateTextSearch {
+            object_type,
+            name,
+            options,
+        })
+    }
+
+    fn parse_alter_text_search_dictionary_option(
+        &mut self,
+    ) -> Result<AlterTextSearchDictionaryOption, ParserError> {
+        let key = self.parse_identifier()?;
+        let value = if self.consume_token(&Token::Eq) {
+            Some(self.parse_expr()?)
+        } else {
+            None
+        };
+        Ok(AlterTextSearchDictionaryOption { key, value })
+    }
+
+    /// Parse a PostgreSQL `ALTER TEXT SEARCH ...` statement.
+    pub fn parse_alter_text_search(&mut self) -> Result<AlterTextSearch, 
ParserError> {
+        self.expect_keywords(&[Keyword::TEXT, Keyword::SEARCH])?;
+        let object_type = self.parse_text_search_object_type()?;
+        let name = self.parse_object_name(false)?;
+
+        let operation = match object_type {
+            TextSearchObjectType::Dictionary => {
+                if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
+                    AlterTextSearchOperation::RenameTo {
+                        new_name: self.parse_identifier()?,
+                    }
+                } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
+                    AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
+                } else if self.parse_keywords(&[Keyword::SET, 
Keyword::SCHEMA]) {
+                    AlterTextSearchOperation::SetSchema {
+                        schema_name: self.parse_object_name(false)?,
+                    }
+                } else if self.consume_token(&Token::LParen) {
+                    let options = self
+                        
.parse_comma_separated(Parser::parse_alter_text_search_dictionary_option)?;
+                    self.expect_token(&Token::RParen)?;
+                    AlterTextSearchOperation::SetOptions { options }
+                } else {
+                    return self.expected_ref(
+                        "RENAME TO, OWNER TO, SET SCHEMA, or (...) after ALTER 
TEXT SEARCH DICTIONARY",
+                        self.peek_token_ref(),
+                    );
+                }
+            }
+            TextSearchObjectType::Configuration => {
+                if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
+                    AlterTextSearchOperation::RenameTo {
+                        new_name: self.parse_identifier()?,
+                    }
+                } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
+                    AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
+                } else if self.parse_keywords(&[Keyword::SET, 
Keyword::SCHEMA]) {
+                    AlterTextSearchOperation::SetSchema {
+                        schema_name: self.parse_object_name(false)?,

Review Comment:
   it looks like these clauses are duplicated for each object type? if so it 
doesn't seem like we need to make the clauses per object type, we  can parse 
which ever ones show up and if a downstream crate needs to validate which 
clauses are valid for which object type they can explicitly do so.



##########
tests/sqlparser_postgres.rs:
##########
@@ -8279,6 +8279,37 @@ fn parse_alter_function_and_aggregate() {
         .is_err());
 }
 
+#[test]
+fn parse_create_and_alter_text_search() {
+    // CREATE — one per object type
+    pg_and_generic().verified_stmt("CREATE TEXT SEARCH DICTIONARY d (template 
= simple)");
+    pg_and_generic().verified_stmt("CREATE TEXT SEARCH CONFIGURATION c (copy = 
english)");
+    pg_and_generic().verified_stmt("CREATE TEXT SEARCH TEMPLATE t (lexize = 
dsimple_lexize)");
+    pg_and_generic().verified_stmt(
+        "CREATE TEXT SEARCH PARSER p (start = prsd_start, gettoken = 
prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype)",
+    );
+
+    // CREATE with quoted option key
+    pg_and_generic().verified_stmt("CREATE TEXT SEARCH TEMPLATE t (\"Init\" = 
init_function)");

Review Comment:
   for the text strings, we can use a raw string literal to avoid manually 
escaping quotes



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to