Skip to content

Commit ea543eb

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 33df166 commit ea543eb

File tree

2 files changed

+156
-2
lines changed

2 files changed

+156
-2
lines changed

src/keywords.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ define_keywords!(
243243
COMPUTE,
244244
CONCURRENTLY,
245245
CONDITION,
246+
CONFIGURATION,
246247
CONFLICT,
247248
CONNECT,
248249
CONNECTION,
@@ -328,6 +329,7 @@ define_keywords!(
328329
DETACH,
329330
DETAIL,
330331
DETERMINISTIC,
332+
DICTIONARY,
331333
DIMENSIONS,
332334
DIRECTORY,
333335
DISABLE,
@@ -756,6 +758,7 @@ define_keywords!(
756758
PARALLEL,
757759
PARAMETER,
758760
PARQUET,
761+
PARSER,
759762
PART,
760763
PARTIAL,
761764
PARTITION,
@@ -1023,6 +1026,7 @@ define_keywords!(
10231026
TASK,
10241027
TBLPROPERTIES,
10251028
TEMP,
1029+
TEMPLATE,
10261030
TEMPORARY,
10271031
TEMPTABLE,
10281032
TERMINATED,

src/parser/mod.rs

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5100,7 +5100,14 @@ impl<'a> Parser<'a> {
51005100
let persistent = dialect_of!(self is DuckDbDialect)
51015101
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
51025102
let create_view_params = self.parse_create_view_params()?;
5103-
if self.parse_keyword(Keyword::TABLE) {
5103+
if self.parse_keywords(&[Keyword::TEXT, Keyword::SEARCH]) {
5104+
if or_replace || or_alter || temporary || global.is_some() || transient || persistent {
5105+
return Err(ParserError::ParserError(
5106+
"CREATE TEXT SEARCH does not support CREATE modifiers".to_string(),
5107+
));
5108+
}
5109+
self.parse_create_text_search().map(Into::into)
5110+
} else if self.parse_keyword(Keyword::TABLE) {
51045111
self.parse_create_table(or_replace, temporary, global, transient)
51055112
.map(Into::into)
51065113
} else if self.peek_keyword(Keyword::MATERIALIZED)
@@ -5173,6 +5180,145 @@ impl<'a> Parser<'a> {
51735180
}
51745181
}
51755182

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

1042910575
/// Parse an `ALTER <object>` statement and dispatch to the appropriate alter handler.
1043010576
pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
10577+
if self.parse_keywords(&[Keyword::TEXT, Keyword::SEARCH]) {
10578+
return self.parse_alter_text_search().map(Into::into);
10579+
}
10580+
1043110581
let object_type = self.expect_one_of_keywords(&[
1043210582
Keyword::VIEW,
1043310583
Keyword::TYPE,
@@ -10487,7 +10637,7 @@ impl<'a> Parser<'a> {
1048710637
Keyword::USER => self.parse_alter_user().map(Into::into),
1048810638
// unreachable because expect_one_of_keywords used above
1048910639
unexpected_keyword => Err(ParserError::ParserError(
10490-
format!("Internal parser error: expected any of {{VIEW, TYPE, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR}}, got {unexpected_keyword:?}"),
10640+
format!("Internal parser error: expected any of {{VIEW, TYPE, TABLE, INDEX, ROLE, POLICY, CONNECTOR, ICEBERG, SCHEMA, USER, OPERATOR, TEXT SEARCH}}, got {unexpected_keyword:?}"),
1049110641
)),
1049210642
}
1049310643
}

0 commit comments

Comments
 (0)