Skip to content

Commit 9ce9e9a

Browse files
Relax text search parser validation
1 parent dabed7d commit 9ce9e9a

File tree

4 files changed

+67
-127
lines changed

4 files changed

+67
-127
lines changed

src/ast/ddl.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5223,7 +5223,7 @@ impl Spanned for AlterOperatorClass {
52235223
}
52245224

52255225
/// PostgreSQL text search object kind.
5226-
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
5226+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
52275227
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52285228
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
52295229
pub enum TextSearchObjectType {
@@ -5235,6 +5235,8 @@ pub enum TextSearchObjectType {
52355235
Template,
52365236
/// `PARSER`
52375237
Parser,
5238+
/// Preserves custom or quoted object kinds for downstream validation.
5239+
Custom(Ident),
52385240
}
52395241

52405242
impl fmt::Display for TextSearchObjectType {
@@ -5244,6 +5246,7 @@ impl fmt::Display for TextSearchObjectType {
52445246
TextSearchObjectType::Configuration => write!(f, "CONFIGURATION"),
52455247
TextSearchObjectType::Template => write!(f, "TEMPLATE"),
52465248
TextSearchObjectType::Parser => write!(f, "PARSER"),
5249+
TextSearchObjectType::Custom(name) => write!(f, "{name}"),
52475250
}
52485251
}
52495252
}
@@ -5279,18 +5282,18 @@ impl Spanned for CreateTextSearch {
52795282
}
52805283
}
52815284

5282-
/// Option assignment used by `ALTER TEXT SEARCH DICTIONARY ... ( ... )`.
5285+
/// Option assignment used by `ALTER TEXT SEARCH ... ( ... )`.
52835286
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
52845287
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52855288
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5286-
pub struct AlterTextSearchDictionaryOption {
5289+
pub struct AlterTextSearchOption {
52875290
/// Option name.
52885291
pub key: Ident,
52895292
/// Optional value (`option [= value]`).
52905293
pub value: Option<Expr>,
52915294
}
52925295

5293-
impl fmt::Display for AlterTextSearchDictionaryOption {
5296+
impl fmt::Display for AlterTextSearchOption {
52945297
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52955298
match &self.value {
52965299
Some(value) => write!(f, "{} = {}", self.key, value),
@@ -5318,8 +5321,8 @@ pub enum AlterTextSearchOperation {
53185321
},
53195322
/// `( option [= value] [, ...] )`
53205323
SetOptions {
5321-
/// Dictionary options to apply.
5322-
options: Vec<AlterTextSearchDictionaryOption>,
5324+
/// Options to apply.
5325+
options: Vec<AlterTextSearchOption>,
53235326
},
53245327
}
53255328

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ pub use self::ddl::{
6464
AlterOperatorClass, AlterOperatorClassOperation, AlterOperatorFamily,
6565
AlterOperatorFamilyOperation, AlterOperatorOperation, AlterPolicy, AlterPolicyOperation,
6666
AlterSchema, AlterSchemaOperation, AlterTable, AlterTableAlgorithm, AlterTableLock,
67-
AlterTableOperation, AlterTableType, AlterTextSearch, AlterTextSearchDictionaryOption,
68-
AlterTextSearchOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition,
67+
AlterTableOperation, AlterTableType, AlterTextSearch, AlterTextSearchOperation,
68+
AlterTextSearchOption, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition,
6969
AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef,
7070
ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, ColumnPolicyProperty,
7171
ConstraintCharacteristics, CreateConnector, CreateDomain, CreateExtension, CreateFunction,

src/parser/mod.rs

Lines changed: 41 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5189,131 +5189,74 @@ impl<'a> Parser<'a> {
51895189
}
51905190

51915191
fn parse_text_search_object_type(&mut self) -> Result<TextSearchObjectType, ParserError> {
5192-
match self.expect_one_of_keywords(&[
5193-
Keyword::DICTIONARY,
5194-
Keyword::CONFIGURATION,
5195-
Keyword::TEMPLATE,
5196-
Keyword::PARSER,
5197-
])? {
5198-
Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
5199-
Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
5200-
Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
5201-
Keyword::PARSER => Ok(TextSearchObjectType::Parser),
5202-
// unreachable because expect_one_of_keywords used above
5203-
unexpected_keyword => Err(ParserError::ParserError(format!(
5204-
"Internal parser error: expected any of {{DICTIONARY, CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}"
5205-
))),
5206-
}
5192+
Ok(if self.parse_keyword(Keyword::DICTIONARY) {
5193+
TextSearchObjectType::Dictionary
5194+
} else if self.parse_keyword(Keyword::CONFIGURATION) {
5195+
TextSearchObjectType::Configuration
5196+
} else if self.parse_keyword(Keyword::TEMPLATE) {
5197+
TextSearchObjectType::Template
5198+
} else if self.parse_keyword(Keyword::PARSER) {
5199+
TextSearchObjectType::Parser
5200+
} else {
5201+
TextSearchObjectType::Custom(self.parse_identifier()?)
5202+
})
52075203
}
52085204

52095205
/// Parse a PostgreSQL `CREATE TEXT SEARCH ...` statement.
52105206
pub fn parse_create_text_search(&mut self) -> Result<CreateTextSearch, ParserError> {
52115207
self.expect_keywords(&[Keyword::TEXT, Keyword::SEARCH])?;
52125208
let object_type = self.parse_text_search_object_type()?;
52135209
let name = self.parse_object_name(false)?;
5214-
self.expect_token(&Token::LParen)?;
5215-
let options = self.parse_comma_separated(Parser::parse_sql_option)?;
5216-
self.expect_token(&Token::RParen)?;
5210+
let options = self
5211+
.parse_parenthesized(|parser| parser.parse_comma_separated(Parser::parse_sql_option))?;
52175212
Ok(CreateTextSearch {
52185213
object_type,
52195214
name,
52205215
options,
52215216
})
52225217
}
52235218

5224-
fn parse_alter_text_search_dictionary_option(
5225-
&mut self,
5226-
) -> Result<AlterTextSearchDictionaryOption, ParserError> {
5219+
fn parse_alter_text_search_option(&mut self) -> Result<AlterTextSearchOption, ParserError> {
52275220
let key = self.parse_identifier()?;
52285221
let value = if self.consume_token(&Token::Eq) {
52295222
Some(self.parse_expr()?)
52305223
} else {
52315224
None
52325225
};
5233-
Ok(AlterTextSearchDictionaryOption { key, value })
5226+
Ok(AlterTextSearchOption { key, value })
5227+
}
5228+
5229+
fn parse_alter_text_search_operation(
5230+
&mut self,
5231+
) -> Result<AlterTextSearchOperation, ParserError> {
5232+
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5233+
Ok(AlterTextSearchOperation::RenameTo {
5234+
new_name: self.parse_identifier()?,
5235+
})
5236+
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
5237+
Ok(AlterTextSearchOperation::OwnerTo(self.parse_owner()?))
5238+
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5239+
Ok(AlterTextSearchOperation::SetSchema {
5240+
schema_name: self.parse_object_name(false)?,
5241+
})
5242+
} else if self.consume_token(&Token::LParen) {
5243+
let options = self.parse_comma_separated(Parser::parse_alter_text_search_option)?;
5244+
self.expect_token(&Token::RParen)?;
5245+
Ok(AlterTextSearchOperation::SetOptions { options })
5246+
} else {
5247+
self.expected_ref(
5248+
"RENAME TO, OWNER TO, SET SCHEMA, or (...)",
5249+
self.peek_token_ref(),
5250+
)
5251+
}
52345252
}
52355253

52365254
/// Parse a PostgreSQL `ALTER TEXT SEARCH ...` statement.
52375255
pub fn parse_alter_text_search(&mut self) -> Result<AlterTextSearch, ParserError> {
52385256
self.expect_keywords(&[Keyword::TEXT, Keyword::SEARCH])?;
52395257
let object_type = self.parse_text_search_object_type()?;
52405258
let name = self.parse_object_name(false)?;
5241-
5242-
let operation = match object_type {
5243-
TextSearchObjectType::Dictionary => {
5244-
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5245-
AlterTextSearchOperation::RenameTo {
5246-
new_name: self.parse_identifier()?,
5247-
}
5248-
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
5249-
AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
5250-
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5251-
AlterTextSearchOperation::SetSchema {
5252-
schema_name: self.parse_object_name(false)?,
5253-
}
5254-
} else if self.consume_token(&Token::LParen) {
5255-
let options = self
5256-
.parse_comma_separated(Parser::parse_alter_text_search_dictionary_option)?;
5257-
self.expect_token(&Token::RParen)?;
5258-
AlterTextSearchOperation::SetOptions { options }
5259-
} else {
5260-
return self.expected_ref(
5261-
"RENAME TO, OWNER TO, SET SCHEMA, or (...) after ALTER TEXT SEARCH DICTIONARY",
5262-
self.peek_token_ref(),
5263-
);
5264-
}
5265-
}
5266-
TextSearchObjectType::Configuration => {
5267-
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5268-
AlterTextSearchOperation::RenameTo {
5269-
new_name: self.parse_identifier()?,
5270-
}
5271-
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
5272-
AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
5273-
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5274-
AlterTextSearchOperation::SetSchema {
5275-
schema_name: self.parse_object_name(false)?,
5276-
}
5277-
} else {
5278-
return self.expected_ref(
5279-
"RENAME TO, OWNER TO, or SET SCHEMA after ALTER TEXT SEARCH CONFIGURATION",
5280-
self.peek_token_ref(),
5281-
);
5282-
}
5283-
}
5284-
TextSearchObjectType::Template => {
5285-
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5286-
AlterTextSearchOperation::RenameTo {
5287-
new_name: self.parse_identifier()?,
5288-
}
5289-
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5290-
AlterTextSearchOperation::SetSchema {
5291-
schema_name: self.parse_object_name(false)?,
5292-
}
5293-
} else {
5294-
return self.expected_ref(
5295-
"RENAME TO or SET SCHEMA after ALTER TEXT SEARCH TEMPLATE",
5296-
self.peek_token_ref(),
5297-
);
5298-
}
5299-
}
5300-
TextSearchObjectType::Parser => {
5301-
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5302-
AlterTextSearchOperation::RenameTo {
5303-
new_name: self.parse_identifier()?,
5304-
}
5305-
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5306-
AlterTextSearchOperation::SetSchema {
5307-
schema_name: self.parse_object_name(false)?,
5308-
}
5309-
} else {
5310-
return self.expected_ref(
5311-
"RENAME TO or SET SCHEMA after ALTER TEXT SEARCH PARSER",
5312-
self.peek_token_ref(),
5313-
);
5314-
}
5315-
}
5316-
};
5259+
let operation = self.parse_alter_text_search_operation()?;
53175260

53185261
Ok(AlterTextSearch {
53195262
object_type,

tests/sqlparser_postgres.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8062,28 +8062,22 @@ fn parse_alter_operator_class() {
80628062

80638063
#[test]
80648064
fn parse_create_and_alter_text_search() {
8065-
// CREATE — one per object type
8066-
pg_and_generic().verified_stmt("CREATE TEXT SEARCH DICTIONARY d (template = simple)");
8067-
pg_and_generic().verified_stmt("CREATE TEXT SEARCH CONFIGURATION c (copy = english)");
8068-
pg_and_generic().verified_stmt("CREATE TEXT SEARCH TEMPLATE t (lexize = dsimple_lexize)");
8069-
pg_and_generic().verified_stmt(
8065+
for sql in [
8066+
"CREATE TEXT SEARCH DICTIONARY d (template = simple)",
8067+
"CREATE TEXT SEARCH CONFIGURATION c (copy = english)",
8068+
"CREATE TEXT SEARCH TEMPLATE t (lexize = dsimple_lexize)",
80708069
"CREATE TEXT SEARCH PARSER p (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype)",
8071-
);
8072-
8073-
// CREATE with quoted option key
8074-
pg_and_generic().verified_stmt("CREATE TEXT SEARCH TEMPLATE t (\"Init\" = init_function)");
8075-
8076-
// ALTER — one test per object type arm, one per operation kind
8077-
pg_and_generic().verified_stmt("ALTER TEXT SEARCH DICTIONARY d (opt = val)");
8078-
pg_and_generic().verified_stmt("ALTER TEXT SEARCH DICTIONARY d (opt)");
8079-
pg_and_generic().verified_stmt("ALTER TEXT SEARCH CONFIGURATION c OWNER TO some_user");
8080-
pg_and_generic().verified_stmt("ALTER TEXT SEARCH TEMPLATE t SET SCHEMA s");
8081-
pg_and_generic().verified_stmt("ALTER TEXT SEARCH PARSER p RENAME TO p2");
8082-
8083-
// Object type must be an unquoted keyword-like token in this position.
8084-
assert!(pg()
8085-
.parse_sql_statements("CREATE TEXT SEARCH \"DICTIONARY\" d (template = simple)")
8086-
.is_err());
8070+
"CREATE TEXT SEARCH \"DICTIONARY\" d (template = simple)",
8071+
"CREATE TEXT SEARCH TEMPLATE t (\"Init\" = init_function)",
8072+
"ALTER TEXT SEARCH DICTIONARY d (opt = val)",
8073+
"ALTER TEXT SEARCH DICTIONARY d (opt)",
8074+
"ALTER TEXT SEARCH CONFIGURATION c OWNER TO some_user",
8075+
"ALTER TEXT SEARCH TEMPLATE t SET SCHEMA s",
8076+
"ALTER TEXT SEARCH PARSER p RENAME TO p2",
8077+
"ALTER TEXT SEARCH TEMPLATE t OWNER TO some_user",
8078+
] {
8079+
pg_and_generic().verified_stmt(sql);
8080+
}
80878081

80888082
// CREATE options are key-value pairs in PostgreSQL syntax.
80898083
assert!(pg()

0 commit comments

Comments
 (0)