Skip to content

Commit a6ad860

Browse files
parser: support PostgreSQL CREATE/ALTER TEXT SEARCH DDL
Add parser support for CREATE/ALTER TEXT SEARCH DICTIONARY,\nCONFIGURATION, TEMPLATE, and PARSER forms, including operation-specific\nALTER clauses and strict CREATE option parsing.\n\nRegister text-search object names as parser keywords and reject unsupported\nCREATE modifiers for text-search objects.
1 parent 973545b commit a6ad860

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

src/keywords.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ define_keywords!(
245245
COMPUTE,
246246
CONCURRENTLY,
247247
CONDITION,
248+
CONFIGURATION,
248249
CONFLICT,
249250
CONNECT,
250251
CONNECTION,
@@ -330,6 +331,7 @@ define_keywords!(
330331
DETACH,
331332
DETAIL,
332333
DETERMINISTIC,
334+
DICTIONARY,
333335
DIMENSIONS,
334336
DIRECTORY,
335337
DISABLE,
@@ -762,6 +764,7 @@ define_keywords!(
762764
PARALLEL,
763765
PARAMETER,
764766
PARQUET,
767+
PARSER,
765768
PART,
766769
PARTIAL,
767770
PARTITION,
@@ -1031,6 +1034,7 @@ define_keywords!(
10311034
TASK,
10321035
TBLPROPERTIES,
10331036
TEMP,
1037+
TEMPLATE,
10341038
TEMPORARY,
10351039
TEMPTABLE,
10361040
TERMINATED,

src/parser/mod.rs

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5113,6 +5113,13 @@ impl<'a> Parser<'a> {
51135113
let create_view_params = self.parse_create_view_params()?;
51145114
if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
51155115
self.parse_create_snapshot_table().map(Into::into)
5116+
} else if self.parse_keywords(&[Keyword::TEXT, Keyword::SEARCH]) {
5117+
if or_replace || or_alter || temporary || global.is_some() || transient || persistent {
5118+
return Err(ParserError::ParserError(
5119+
"CREATE TEXT SEARCH does not support CREATE modifiers".to_string(),
5120+
));
5121+
}
5122+
self.parse_create_text_search().map(Into::into)
51165123
} else if self.parse_keyword(Keyword::TABLE) {
51175124
self.parse_create_table(or_replace, temporary, global, transient)
51185125
.map(Into::into)
@@ -5186,6 +5193,145 @@ impl<'a> Parser<'a> {
51865193
}
51875194
}
51885195

5196+
fn parse_text_search_object_type(&mut self) -> Result<TextSearchObjectType, ParserError> {
5197+
match self.expect_one_of_keywords(&[
5198+
Keyword::DICTIONARY,
5199+
Keyword::CONFIGURATION,
5200+
Keyword::TEMPLATE,
5201+
Keyword::PARSER,
5202+
])? {
5203+
Keyword::DICTIONARY => Ok(TextSearchObjectType::Dictionary),
5204+
Keyword::CONFIGURATION => Ok(TextSearchObjectType::Configuration),
5205+
Keyword::TEMPLATE => Ok(TextSearchObjectType::Template),
5206+
Keyword::PARSER => Ok(TextSearchObjectType::Parser),
5207+
// unreachable because expect_one_of_keywords used above
5208+
unexpected_keyword => Err(ParserError::ParserError(format!(
5209+
"Internal parser error: expected any of {{DICTIONARY, CONFIGURATION, TEMPLATE, PARSER}}, got {unexpected_keyword:?}"
5210+
))),
5211+
}
5212+
}
5213+
5214+
fn parse_text_search_option(&mut self) -> Result<SqlOption, ParserError> {
5215+
let key = self.parse_identifier()?;
5216+
self.expect_token(&Token::Eq)?;
5217+
let value = self.parse_expr()?;
5218+
Ok(SqlOption::KeyValue { key, value })
5219+
}
5220+
5221+
/// Parse a PostgreSQL `CREATE TEXT SEARCH ...` statement.
5222+
pub fn parse_create_text_search(&mut self) -> Result<CreateTextSearch, ParserError> {
5223+
let object_type = self.parse_text_search_object_type()?;
5224+
let name = self.parse_object_name(false)?;
5225+
self.expect_token(&Token::LParen)?;
5226+
let options = self.parse_comma_separated(Parser::parse_text_search_option)?;
5227+
self.expect_token(&Token::RParen)?;
5228+
Ok(CreateTextSearch {
5229+
object_type,
5230+
name,
5231+
options,
5232+
})
5233+
}
5234+
5235+
fn parse_alter_text_search_dictionary_option(
5236+
&mut self,
5237+
) -> Result<AlterTextSearchDictionaryOption, ParserError> {
5238+
let key = self.parse_identifier()?;
5239+
let value = if self.consume_token(&Token::Eq) {
5240+
Some(self.parse_expr()?)
5241+
} else {
5242+
None
5243+
};
5244+
Ok(AlterTextSearchDictionaryOption { key, value })
5245+
}
5246+
5247+
/// Parse a PostgreSQL `ALTER TEXT SEARCH ...` statement.
5248+
pub fn parse_alter_text_search(&mut self) -> Result<AlterTextSearch, ParserError> {
5249+
let object_type = self.parse_text_search_object_type()?;
5250+
let name = self.parse_object_name(false)?;
5251+
5252+
let operation = match object_type {
5253+
TextSearchObjectType::Dictionary => {
5254+
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5255+
AlterTextSearchOperation::RenameTo {
5256+
new_name: self.parse_identifier()?,
5257+
}
5258+
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
5259+
AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
5260+
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5261+
AlterTextSearchOperation::SetSchema {
5262+
schema_name: self.parse_object_name(false)?,
5263+
}
5264+
} else if self.consume_token(&Token::LParen) {
5265+
let options = self
5266+
.parse_comma_separated(Parser::parse_alter_text_search_dictionary_option)?;
5267+
self.expect_token(&Token::RParen)?;
5268+
AlterTextSearchOperation::SetOptions { options }
5269+
} else {
5270+
return self.expected_ref(
5271+
"RENAME TO, OWNER TO, SET SCHEMA, or (...) after ALTER TEXT SEARCH DICTIONARY",
5272+
self.peek_token_ref(),
5273+
);
5274+
}
5275+
}
5276+
TextSearchObjectType::Configuration => {
5277+
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5278+
AlterTextSearchOperation::RenameTo {
5279+
new_name: self.parse_identifier()?,
5280+
}
5281+
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
5282+
AlterTextSearchOperation::OwnerTo(self.parse_owner()?)
5283+
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5284+
AlterTextSearchOperation::SetSchema {
5285+
schema_name: self.parse_object_name(false)?,
5286+
}
5287+
} else {
5288+
return self.expected_ref(
5289+
"RENAME TO, OWNER TO, or SET SCHEMA after ALTER TEXT SEARCH CONFIGURATION",
5290+
self.peek_token_ref(),
5291+
);
5292+
}
5293+
}
5294+
TextSearchObjectType::Template => {
5295+
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5296+
AlterTextSearchOperation::RenameTo {
5297+
new_name: self.parse_identifier()?,
5298+
}
5299+
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5300+
AlterTextSearchOperation::SetSchema {
5301+
schema_name: self.parse_object_name(false)?,
5302+
}
5303+
} else {
5304+
return self.expected_ref(
5305+
"RENAME TO or SET SCHEMA after ALTER TEXT SEARCH TEMPLATE",
5306+
self.peek_token_ref(),
5307+
);
5308+
}
5309+
}
5310+
TextSearchObjectType::Parser => {
5311+
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
5312+
AlterTextSearchOperation::RenameTo {
5313+
new_name: self.parse_identifier()?,
5314+
}
5315+
} else if self.parse_keywords(&[Keyword::SET, Keyword::SCHEMA]) {
5316+
AlterTextSearchOperation::SetSchema {
5317+
schema_name: self.parse_object_name(false)?,
5318+
}
5319+
} else {
5320+
return self.expected_ref(
5321+
"RENAME TO or SET SCHEMA after ALTER TEXT SEARCH PARSER",
5322+
self.peek_token_ref(),
5323+
);
5324+
}
5325+
}
5326+
};
5327+
5328+
Ok(AlterTextSearch {
5329+
object_type,
5330+
name,
5331+
operation,
5332+
})
5333+
}
5334+
51895335
fn parse_create_user(&mut self, or_replace: bool) -> Result<CreateUser, ParserError> {
51905336
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
51915337
let name = self.parse_identifier()?;
@@ -10577,6 +10723,10 @@ impl<'a> Parser<'a> {
1057710723

1057810724
/// Parse an `ALTER <object>` statement and dispatch to the appropriate alter handler.
1057910725
pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
10726+
if self.parse_keywords(&[Keyword::TEXT, Keyword::SEARCH]) {
10727+
return self.parse_alter_text_search().map(Into::into);
10728+
}
10729+
1058010730
let object_type = self.expect_one_of_keywords(&[
1058110731
Keyword::VIEW,
1058210732
Keyword::TYPE,
@@ -10636,7 +10786,7 @@ impl<'a> Parser<'a> {
1063610786
Keyword::USER => self.parse_alter_user().map(Into::into),
1063710787
// unreachable because expect_one_of_keywords used above
1063810788
unexpected_keyword => Err(ParserError::ParserError(
10639-
format!("Internal parser error: expected any of {{VIEW, TYPE, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"),
10789+
format!("Internal parser error: expected any of {{VIEW, TYPE, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR, TEXT SEARCH}}, got {unexpected_keyword:?}"),
1064010790
)),
1064110791
}
1064210792
}

0 commit comments

Comments
 (0)