From 3d2a225dcb341b135dad414b0cad4e021e83ac87 Mon Sep 17 00:00:00 2001 From: Aleksandar Maksimovic Date: Tue, 14 Apr 2026 11:41:07 -0700 Subject: [PATCH] Add ASYNC keyword support for CREATE INDEX Amazon Aurora DSQL uses `CREATE INDEX ASYNC` to create indexes asynchronously. This adds parsing support for the ASYNC keyword in CREATE INDEX statements. Changes: - Add ASYNC keyword to the keyword list - Add `async` field to CreateIndex struct - Parse ASYNC keyword after CONCURRENTLY in parse_create_index - Round-trip support via Display impl - Tests for CREATE INDEX ASYNC and CREATE UNIQUE INDEX ASYNC --- src/ast/ddl.rs | 5 ++- src/ast/spans.rs | 1 + src/keywords.rs | 1 + src/parser/mod.rs | 2 ++ tests/sqlparser_common.rs | 4 +++ tests/sqlparser_postgres.rs | 70 +++++++++++++++++++++++++++++++++++++ 6 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1f0432909..29f46ab37 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2818,6 +2818,8 @@ pub struct CreateIndex { pub unique: bool, /// whether the index is created concurrently pub concurrently: bool, + /// whether the index is created asynchronously + pub r#async: bool, /// IF NOT EXISTS clause pub if_not_exists: bool, /// INCLUDE clause: @@ -2843,13 +2845,14 @@ impl fmt::Display for CreateIndex { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "CREATE {unique}INDEX {concurrently}{if_not_exists}", + "CREATE {unique}INDEX {concurrently}{async_}{if_not_exists}", unique = if self.unique { "UNIQUE " } else { "" }, concurrently = if self.concurrently { "CONCURRENTLY " } else { "" }, + async_ = if self.r#async { "ASYNC " } else { "" }, if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 9a32dc5a0..bbbad5dba 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -682,6 +682,7 @@ impl Spanned for CreateIndex { columns, unique: _, // bool concurrently: _, // bool + r#async: _, // bool if_not_exists: _, // bool include, nulls_distinct: _, // bool diff --git a/src/keywords.rs b/src/keywords.rs index 4ed30d243..f8cc1ba0b 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -131,6 +131,7 @@ define_keywords!( ASOF, ASSERT, ASYMMETRIC, + ASYNC, AT, ATOMIC, ATTACH, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 69a3605fa..86821d46b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8029,6 +8029,7 @@ impl<'a> Parser<'a> { /// Parse a `CREATE INDEX` statement. pub fn parse_create_index(&mut self, unique: bool) -> Result { let concurrently = self.parse_keyword(Keyword::CONCURRENTLY); + let r#async = self.parse_keyword(Keyword::ASYNC); let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let mut using = None; @@ -8108,6 +8109,7 @@ impl<'a> Parser<'a> { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 80c37588a..1e0f28ff4 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -9541,6 +9541,7 @@ fn test_create_index_with_using_function() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -9555,6 +9556,7 @@ fn test_create_index_with_using_function() { assert_eq!(indexed_columns, columns); assert!(unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert!(include.is_empty()); assert!(with.is_empty()); @@ -9596,6 +9598,7 @@ fn test_create_index_with_with_clause() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -9609,6 +9612,7 @@ fn test_create_index_with_with_clause() { pretty_assertions::assert_eq!(indexed_columns, columns); assert!(unique); assert!(!concurrently); + assert!(!r#async); assert!(!if_not_exists); assert!(include.is_empty()); pretty_assertions::assert_eq!(with_parameters, with); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 07b62dd93..0d32e45a4 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2806,6 +2806,7 @@ fn parse_create_index() { columns, unique, concurrently, + r#async, if_not_exists, nulls_distinct: None, include, @@ -2819,6 +2820,7 @@ fn parse_create_index() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty()); @@ -2841,6 +2843,7 @@ fn parse_create_anonymous_index() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -2854,6 +2857,7 @@ fn parse_create_anonymous_index() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(!if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty()); @@ -2959,6 +2963,7 @@ fn parse_create_indices_with_operator_classes() { columns, unique: false, concurrently: false, + r#async: false, if_not_exists: false, include, nulls_distinct: None, @@ -2987,6 +2992,7 @@ fn parse_create_indices_with_operator_classes() { columns, unique: false, concurrently: false, + r#async: false, if_not_exists: false, include, nulls_distinct: None, @@ -3070,6 +3076,7 @@ fn parse_create_bloom() { columns, unique: false, concurrently: false, + r#async: false, if_not_exists: false, include, nulls_distinct: None, @@ -3126,6 +3133,7 @@ fn parse_create_brin() { columns, unique: false, concurrently: false, + r#async: false, if_not_exists: false, include, nulls_distinct: None, @@ -3193,6 +3201,7 @@ fn parse_create_index_concurrently() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -3206,6 +3215,7 @@ fn parse_create_index_concurrently() { assert_eq!(None, using); assert!(!unique); assert!(concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty()); @@ -3217,6 +3227,58 @@ fn parse_create_index_concurrently() { } } +#[test] +fn parse_create_index_async() { + let sql = "CREATE INDEX ASYNC my_index ON my_table(col1)"; + match pg().verified_stmt(sql) { + Statement::CreateIndex(CreateIndex { + name: Some(ObjectName(name)), + table_name: ObjectName(table_name), + using, + columns, + unique, + concurrently, + r#async, + if_not_exists, + include, + nulls_distinct: None, + with, + predicate: None, + index_options, + alter_options, + }) => { + assert_eq_vec(&["my_index"], &name); + assert_eq_vec(&["my_table"], &table_name); + assert_eq!(None, using); + assert!(!unique); + assert!(!concurrently); + assert!(r#async); + assert!(!if_not_exists); + assert_eq_vec(&["col1"], &columns); + assert!(include.is_empty()); + assert!(with.is_empty()); + assert!(index_options.is_empty()); + assert!(alter_options.is_empty()); + } + _ => unreachable!(), + } + + let sql = "CREATE UNIQUE INDEX ASYNC my_index ON my_table(col1)"; + match pg().verified_stmt(sql) { + Statement::CreateIndex(CreateIndex { + unique, + concurrently, + r#async, + .. + }) => { + assert!(unique); + assert!(!concurrently); + assert!(r#async); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_index_with_predicate() { let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2) WHERE col3 IS NULL"; @@ -3228,6 +3290,7 @@ fn parse_create_index_with_predicate() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -3241,6 +3304,7 @@ fn parse_create_index_with_predicate() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty()); @@ -3263,6 +3327,7 @@ fn parse_create_index_with_include() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: None, @@ -3276,6 +3341,7 @@ fn parse_create_index_with_include() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert_eq_vec(&["col3", "col4"], &include); @@ -3298,6 +3364,7 @@ fn parse_create_index_with_nulls_distinct() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: Some(nulls_distinct), @@ -3311,6 +3378,7 @@ fn parse_create_index_with_nulls_distinct() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty()); @@ -3331,6 +3399,7 @@ fn parse_create_index_with_nulls_distinct() { columns, unique, concurrently, + r#async, if_not_exists, include, nulls_distinct: Some(nulls_distinct), @@ -3344,6 +3413,7 @@ fn parse_create_index_with_nulls_distinct() { assert_eq!(None, using); assert!(!unique); assert!(!concurrently); + assert!(!r#async); assert!(if_not_exists); assert_eq_vec(&["col1", "col2"], &columns); assert!(include.is_empty());