From 7b2e06b69a61fa357837b53b5cd8399b4f8110d5 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 27 Sep 2026 00:14:16 -0700 Subject: [PATCH] Parse and ignore NULLS FIRST / NULLS LAST in index columns Postgres supports NULLS FIRST / NULLS LAST in the column specification of an index. Parse it and ignore it. --- qusql-parse/src/alter_table.rs | 4 ++++ qusql-parse/src/create_index.rs | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/qusql-parse/src/alter_table.rs b/qusql-parse/src/alter_table.rs index e86e901b..08c63982 100644 --- a/qusql-parse/src/alter_table.rs +++ b/qusql-parse/src/alter_table.rs @@ -189,6 +189,8 @@ pub struct IndexCol<'a> { pub asc: Option, /// Optional DESC ordering pub desc: Option, + /// Optional NULLS FIRST / NULLS LAST ordering; bool is `true` if FIRST + pub nulls_first_last: Option<(bool, Span)>, } impl<'a> Spanned for IndexCol<'a> { @@ -198,6 +200,7 @@ impl<'a> Spanned for IndexCol<'a> { .join_span(&self.opclass) .join_span(&self.asc) .join_span(&self.desc) + .join_span(&self.nulls_first_last) } } @@ -1709,6 +1712,7 @@ pub(crate) fn parse_index_cols<'a>( opclass, asc, desc, + nulls_first_last: None, }); if parser.skip_token(Token::Comma).is_none() { break; diff --git a/qusql-parse/src/create_index.rs b/qusql-parse/src/create_index.rs index 80877215..682c8a1b 100644 --- a/qusql-parse/src/create_index.rs +++ b/qusql-parse/src/create_index.rs @@ -270,12 +270,27 @@ pub(crate) fn parse_create_index<'a>( None }; + // Parse optional NULLS FIRST | NULLS LAST + let nulls_first_last = if let Some(nulls_span) = parser.skip_keyword(Keyword::NULLS) { + if let Some(first_span) = parser.skip_keyword(Keyword::FIRST) { + Some((true, nulls_span.join_span(&first_span))) + } else if let Some(last_span) = parser.skip_keyword(Keyword::LAST) { + Some((false, nulls_span.join_span(&last_span))) + } else { + return parser.err_here("Expected FIRST or LAST after NULLS"); + } + } else { + None + }; + parser.postgres_only(&nulls_first_last); + column_names.push(IndexCol { expr, size, opclass, asc, desc, + nulls_first_last, }); if parser.skip_token(Token::Comma).is_none() {