From 6ec9508e175359a365090d9efcd0f122a75a76ea Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Wed, 12 Aug 2026 11:05:56 -0700 Subject: [PATCH 1/4] BigQuery: support `CREATE VECTOR INDEX` BigQuery can create a vector index for approximate nearest-neighbor search over an embedding column: CREATE [OR REPLACE] VECTOR INDEX [IF NOT EXISTS] ON () OPTIONS(index_type = 'IVF', distance_type = 'COSINE', ...) `VECTOR` is not a keyword, so `parse_create` previously failed with `Expected: an object type after CREATE, found: VECTOR`. - Add `vector`, `or_replace` and `options` fields to `CreateIndex`. `VECTOR` is a modifier on `CREATE INDEX` (like `EXTERNAL` on `CREATE TABLE`), so it reuses the existing node rather than a new statement variant. `Display` renders `CREATE [OR REPLACE ]VECTOR INDEX ...` plus a trailing `OPTIONS(...)`. - Parse the form in `parse_create` via a small `parse_create_vector_index` helper; the `OPTIONS(...)` clause reuses `parse_options` / `SqlOption`, so it parses and renders the same way as `CREATE TABLE` / `CREATE VIEW` OPTIONS. - Plain `CREATE INDEX` is unchanged (all three fields default to false/empty). - New test `parse_bigquery_create_vector_index` in `tests/sqlparser_bigquery.rs` verifies the round-trip and covers `OR REPLACE`, `IF NOT EXISTS`, multi-part names and the OPTIONS-less form. Docs: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_vector_index_statement Co-authored-by: Claude Opus 4.8 --- src/ast/ddl.rs | 13 ++++++- src/ast/spans.rs | 4 ++ src/parser/mod.rs | 40 +++++++++++++++++++ tests/sqlparser_bigquery.rs | 78 +++++++++++++++++++++++++++++++++++++ tests/sqlparser_common.rs | 6 +++ tests/sqlparser_postgres.rs | 33 ++++++++++++++++ 6 files changed, 173 insertions(+), 1 deletion(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1247826c9f..92fa3f7511 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2895,6 +2895,10 @@ pub struct CreateIndex { pub using: Option, /// columns included in the index pub columns: Vec, + /// whether this is a BigQuery `CREATE VECTOR INDEX` + pub vector: bool, + /// whether the statement is `CREATE OR REPLACE` + pub or_replace: bool, /// whether the index is unique pub unique: bool, /// whether the index is created concurrently @@ -2911,6 +2915,8 @@ pub struct CreateIndex { pub nulls_distinct: Option, /// WITH clause: pub with: Vec, + /// BigQuery `OPTIONS(...)` clause, e.g. on `CREATE VECTOR INDEX` + pub options: Vec, /// WHERE clause: pub predicate: Option, /// Index options: @@ -2928,8 +2934,10 @@ impl fmt::Display for CreateIndex { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "CREATE {unique}INDEX {concurrently}{async_}{if_not_exists}", + "CREATE {or_replace}{unique}{vector}INDEX {concurrently}{async_}{if_not_exists}", + or_replace = if self.or_replace { "OR REPLACE " } else { "" }, unique = if self.unique { "UNIQUE " } else { "" }, + vector = if self.vector { "VECTOR " } else { "" }, concurrently = if self.concurrently { "CONCURRENTLY " } else { @@ -2950,6 +2958,9 @@ impl fmt::Display for CreateIndex { write!(f, " USING {value} ")?; } write!(f, "({})", display_comma_separated(&self.columns))?; + if !self.options.is_empty() { + write!(f, " OPTIONS({})", display_comma_separated(&self.options))?; + } if !self.include.is_empty() { write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?; } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 704b583b8b..c8759ab27b 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -747,6 +747,8 @@ impl Spanned for CreateIndex { table_name, using: _, columns, + vector: _, // bool + or_replace: _, // bool unique: _, // bool concurrently: _, // bool r#async: _, // bool @@ -754,6 +756,7 @@ impl Spanned for CreateIndex { include, nulls_distinct: _, // bool with, + options, predicate, index_options: _, alter_options, @@ -766,6 +769,7 @@ impl Spanned for CreateIndex { .chain(columns.iter().map(|i| i.column.span())) .chain(include.iter().map(|i| i.span)) .chain(with.iter().map(|i| i.span())) + .chain(options.iter().map(|i| i.span())) .chain(predicate.iter().map(|i| i.span())) .chain(alter_options.iter().map(|i| i.span())), ) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f86406fb26..db708137bd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5565,6 +5565,13 @@ impl<'a> Parser<'a> { self.parse_create_schema(or_replace) } else if self.parse_keyword(Keyword::WAREHOUSE) { self.parse_create_warehouse(or_replace).map(Into::into) + } else if matches!( + &self.peek_token_ref().token, + Token::Word(w) if w.keyword == Keyword::NoKeyword && w.value.eq_ignore_ascii_case("VECTOR") + ) { + // BigQuery `CREATE [OR REPLACE] VECTOR INDEX ...`; VECTOR is not a keyword. + self.next_token(); + self.parse_create_vector_index(or_replace).map(Into::into) } else if or_replace { self.expected_ref( "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION or AGGREGATE or SCHEMA or WAREHOUSE after CREATE OR REPLACE", @@ -8833,6 +8840,36 @@ impl<'a> Parser<'a> { Ok(Statement::Discard { object_type }) } + /// Parse a BigQuery `CREATE [OR REPLACE] VECTOR INDEX` statement (`VECTOR` already consumed). + fn parse_create_vector_index(&mut self, or_replace: bool) -> Result { + self.expect_keyword_is(Keyword::INDEX)?; + let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); + let name = self.parse_object_name(false)?; + self.expect_keyword_is(Keyword::ON)?; + let table_name = self.parse_object_name(false)?; + let columns = self.parse_parenthesized_index_column_list()?; + let options = self.parse_options(Keyword::OPTIONS)?; + Ok(CreateIndex { + name: Some(name), + table_name, + using: None, + columns, + vector: true, + or_replace, + unique: false, + concurrently: false, + r#async: false, + if_not_exists, + include: vec![], + nulls_distinct: None, + with: vec![], + options, + predicate: None, + index_options: vec![], + alter_options: vec![], + }) + } + /// Parse a `CREATE INDEX` statement. pub fn parse_create_index(&mut self, unique: bool) -> Result { let concurrently = self.parse_keyword(Keyword::CONCURRENTLY); @@ -8914,6 +8951,8 @@ impl<'a> Parser<'a> { table_name, using, columns, + vector: false, + or_replace: false, unique, concurrently, r#async, @@ -8921,6 +8960,7 @@ impl<'a> Parser<'a> { include, nulls_distinct, with, + options: vec![], predicate, index_options, alter_options, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index b110423370..ed9fcd137f 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -3001,3 +3001,81 @@ fn test_byte_and_raw_string_quote_escaping() { err ); } + +#[test] +fn parse_bigquery_create_vector_index() { + // `CREATE VECTOR INDEX ... OPTIONS(...)` is a CreateIndex flagged `vector`. + let sql = + "CREATE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE', dimension = 4)"; + match bigquery().verified_stmt(sql) { + Statement::CreateIndex(CreateIndex { + name, + table_name, + using, + columns, + vector, + or_replace, + unique, + if_not_exists, + with, + options, + .. + }) => { + assert!(vector); + assert!(!or_replace); + assert!(!unique); + assert!(!if_not_exists); + assert_eq!(name.unwrap().to_string(), "emb"); + assert_eq!(table_name.to_string(), "t"); + assert_eq!(using, None); + assert!(with.is_empty()); + assert_eq!( + columns.iter().map(|c| c.to_string()).collect::>(), + vec!["embedding"] + ); + assert_eq!( + options, + vec![ + SqlOption::KeyValue { + key: Ident::new("distance_type"), + value: Expr::Value( + Value::SingleQuotedString("COSINE".to_string()).with_empty_span() + ), + }, + SqlOption::KeyValue { + key: Ident::new("dimension"), + value: Expr::value(number("4")), + }, + ] + ); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + + // OR REPLACE, IF NOT EXISTS, multi-part names, and the OPTIONS-less form all round-trip. + match bigquery().verified_stmt( + "CREATE OR REPLACE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE')", + ) { + Statement::CreateIndex(CreateIndex { + vector, or_replace, .. + }) => { + assert!(vector); + assert!(or_replace); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + match bigquery().verified_stmt( + "CREATE VECTOR INDEX IF NOT EXISTS mydataset.emb ON mydataset.t(embedding) OPTIONS(distance_type = 'EUCLIDEAN')", + ) { + Statement::CreateIndex(CreateIndex { + vector, + if_not_exists, + .. + }) => { + assert!(vector); + assert!(if_not_exists); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + bigquery().verified_stmt("CREATE VECTOR INDEX emb ON t(embedding)"); +} diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 379e37a660..e65073e262 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -10443,6 +10443,9 @@ fn test_create_index_with_using_function() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq!("idx_name", name.to_string()); assert_eq!("test", table_name.to_string()); @@ -10500,6 +10503,9 @@ fn test_create_index_with_with_clause() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { pretty_assertions::assert_eq!("title_idx", name.to_string()); pretty_assertions::assert_eq!("films", table_name.to_string()); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 7c5ac8712d..59c9457a04 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3294,6 +3294,9 @@ fn parse_create_index() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3331,6 +3334,9 @@ fn parse_create_anonymous_index() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq!(None, name); assert_eq_vec(&["my_table"], &table_name); @@ -3451,6 +3457,9 @@ fn parse_create_indices_with_operator_classes() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3480,6 +3489,9 @@ fn parse_create_indices_with_operator_classes() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3564,6 +3576,9 @@ fn parse_create_bloom() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["bloomidx"], &name); assert_eq_vec(&["tbloom"], &table_name); @@ -3621,6 +3636,9 @@ fn parse_create_brin() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["brin_sensor_data_recorded_at"], &name); assert_eq_vec(&["sensor_data"], &table_name); @@ -3689,6 +3707,9 @@ fn parse_create_index_concurrently() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3726,6 +3747,9 @@ fn parse_create_index_with_predicate() { predicate: Some(_), index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3763,6 +3787,9 @@ fn parse_create_index_with_include() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3800,6 +3827,9 @@ fn parse_create_index_with_nulls_distinct() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3835,6 +3865,9 @@ fn parse_create_index_with_nulls_distinct() { predicate: None, index_options, alter_options, + vector: _, + or_replace: _, + options: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); From a09b8584e34385664e6fdb055e8a19429f52d2f7 Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Tue, 18 Aug 2026 12:52:03 -0700 Subject: [PATCH 2/4] Generalize `CREATE VECTOR INDEX` beyond BigQuery Address review feedback that `CREATE VECTOR INDEX` is not BigQuery-specific (Oracle, SQL Server, MariaDB and TiDB also have it, hence Generic too). - Route the statement through `parse_create_index` instead of a separate BigQuery helper, so it inherits the standard index trailers (`USING`, `INCLUDE`, `WITH`, expression targets, index options) that cover the Oracle / SQL Server / TiDB variants, plus the BigQuery `OPTIONS(...)` clause. - Align `Display` order (OPTIONS after WITH) with parse order so `INCLUDE` + `OPTIONS` combinations round-trip. - Drop the BigQuery-specific doc-comment framing. - Move the test to `tests/sqlparser_common.rs` as `parse_create_vector_index`, running across all dialects and covering `OR REPLACE`, `IF NOT EXISTS`, schema-qualified names, `OPTIONS(...)`, the `INCLUDE` trailer, and an expression target. Co-authored-by: Claude Opus 4.8 --- src/ast/ddl.rs | 10 ++--- src/parser/mod.rs | 56 +++++++++--------------- tests/sqlparser_bigquery.rs | 78 --------------------------------- tests/sqlparser_common.rs | 86 +++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 118 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 92fa3f7511..9d3be807cd 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2895,7 +2895,7 @@ pub struct CreateIndex { pub using: Option, /// columns included in the index pub columns: Vec, - /// whether this is a BigQuery `CREATE VECTOR INDEX` + /// whether this is a `CREATE VECTOR INDEX` pub vector: bool, /// whether the statement is `CREATE OR REPLACE` pub or_replace: bool, @@ -2915,7 +2915,7 @@ pub struct CreateIndex { pub nulls_distinct: Option, /// WITH clause: pub with: Vec, - /// BigQuery `OPTIONS(...)` clause, e.g. on `CREATE VECTOR INDEX` + /// `OPTIONS(...)` clause, e.g. on `CREATE VECTOR INDEX` pub options: Vec, /// WHERE clause: pub predicate: Option, @@ -2958,9 +2958,6 @@ impl fmt::Display for CreateIndex { write!(f, " USING {value} ")?; } write!(f, "({})", display_comma_separated(&self.columns))?; - if !self.options.is_empty() { - write!(f, " OPTIONS({})", display_comma_separated(&self.options))?; - } if !self.include.is_empty() { write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?; } @@ -2974,6 +2971,9 @@ impl fmt::Display for CreateIndex { if !self.with.is_empty() { write!(f, " WITH ({})", display_comma_separated(&self.with))?; } + if !self.options.is_empty() { + write!(f, " OPTIONS({})", display_comma_separated(&self.options))?; + } if let Some(predicate) = &self.predicate { write!(f, " WHERE {predicate}")?; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index db708137bd..8c0e816e64 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5569,9 +5569,11 @@ impl<'a> Parser<'a> { &self.peek_token_ref().token, Token::Word(w) if w.keyword == Keyword::NoKeyword && w.value.eq_ignore_ascii_case("VECTOR") ) { - // BigQuery `CREATE [OR REPLACE] VECTOR INDEX ...`; VECTOR is not a keyword. + // `CREATE [OR REPLACE] VECTOR INDEX ...`; VECTOR is not a keyword. self.next_token(); - self.parse_create_vector_index(or_replace).map(Into::into) + self.expect_keyword_is(Keyword::INDEX)?; + self.parse_create_index_inner(false, true, or_replace) + .map(Into::into) } else if or_replace { self.expected_ref( "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION or AGGREGATE or SCHEMA or WAREHOUSE after CREATE OR REPLACE", @@ -8840,38 +8842,19 @@ impl<'a> Parser<'a> { Ok(Statement::Discard { object_type }) } - /// Parse a BigQuery `CREATE [OR REPLACE] VECTOR INDEX` statement (`VECTOR` already consumed). - fn parse_create_vector_index(&mut self, or_replace: bool) -> Result { - self.expect_keyword_is(Keyword::INDEX)?; - let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); - let name = self.parse_object_name(false)?; - self.expect_keyword_is(Keyword::ON)?; - let table_name = self.parse_object_name(false)?; - let columns = self.parse_parenthesized_index_column_list()?; - let options = self.parse_options(Keyword::OPTIONS)?; - Ok(CreateIndex { - name: Some(name), - table_name, - using: None, - columns, - vector: true, - or_replace, - unique: false, - concurrently: false, - r#async: false, - if_not_exists, - include: vec![], - nulls_distinct: None, - with: vec![], - options, - predicate: None, - index_options: vec![], - alter_options: vec![], - }) - } - /// Parse a `CREATE INDEX` statement. pub fn parse_create_index(&mut self, unique: bool) -> Result { + self.parse_create_index_inner(unique, false, false) + } + + /// Parse the body of a `CREATE [UNIQUE | VECTOR] INDEX` statement, with the + /// leading `[UNIQUE | VECTOR] INDEX` keyword(s) already consumed. + fn parse_create_index_inner( + &mut self, + unique: bool, + vector: bool, + or_replace: 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]); @@ -8924,6 +8907,9 @@ impl<'a> Parser<'a> { Vec::new() }; + // `OPTIONS(...)` clause (e.g. `CREATE VECTOR INDEX`); no-op when absent. + let options = self.parse_options(Keyword::OPTIONS)?; + let predicate = if self.parse_keyword(Keyword::WHERE) { Some(self.parse_expr()?) } else { @@ -8951,8 +8937,8 @@ impl<'a> Parser<'a> { table_name, using, columns, - vector: false, - or_replace: false, + vector, + or_replace, unique, concurrently, r#async, @@ -8960,7 +8946,7 @@ impl<'a> Parser<'a> { include, nulls_distinct, with, - options: vec![], + options, predicate, index_options, alter_options, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index ed9fcd137f..b110423370 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -3001,81 +3001,3 @@ fn test_byte_and_raw_string_quote_escaping() { err ); } - -#[test] -fn parse_bigquery_create_vector_index() { - // `CREATE VECTOR INDEX ... OPTIONS(...)` is a CreateIndex flagged `vector`. - let sql = - "CREATE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE', dimension = 4)"; - match bigquery().verified_stmt(sql) { - Statement::CreateIndex(CreateIndex { - name, - table_name, - using, - columns, - vector, - or_replace, - unique, - if_not_exists, - with, - options, - .. - }) => { - assert!(vector); - assert!(!or_replace); - assert!(!unique); - assert!(!if_not_exists); - assert_eq!(name.unwrap().to_string(), "emb"); - assert_eq!(table_name.to_string(), "t"); - assert_eq!(using, None); - assert!(with.is_empty()); - assert_eq!( - columns.iter().map(|c| c.to_string()).collect::>(), - vec!["embedding"] - ); - assert_eq!( - options, - vec![ - SqlOption::KeyValue { - key: Ident::new("distance_type"), - value: Expr::Value( - Value::SingleQuotedString("COSINE".to_string()).with_empty_span() - ), - }, - SqlOption::KeyValue { - key: Ident::new("dimension"), - value: Expr::value(number("4")), - }, - ] - ); - } - other => panic!("expected CreateIndex, got {other:?}"), - } - - // OR REPLACE, IF NOT EXISTS, multi-part names, and the OPTIONS-less form all round-trip. - match bigquery().verified_stmt( - "CREATE OR REPLACE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE')", - ) { - Statement::CreateIndex(CreateIndex { - vector, or_replace, .. - }) => { - assert!(vector); - assert!(or_replace); - } - other => panic!("expected CreateIndex, got {other:?}"), - } - match bigquery().verified_stmt( - "CREATE VECTOR INDEX IF NOT EXISTS mydataset.emb ON mydataset.t(embedding) OPTIONS(distance_type = 'EUCLIDEAN')", - ) { - Statement::CreateIndex(CreateIndex { - vector, - if_not_exists, - .. - }) => { - assert!(vector); - assert!(if_not_exists); - } - other => panic!("expected CreateIndex, got {other:?}"), - } - bigquery().verified_stmt("CREATE VECTOR INDEX emb ON t(embedding)"); -} diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e65073e262..751a3feec4 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -10580,6 +10580,92 @@ fn parse_create_index_async() { verified_stmt("CREATE UNIQUE INDEX ASYNC my_index ON my_table(col1)"); } +#[test] +fn parse_create_vector_index() { + // `CREATE VECTOR INDEX` parses for every dialect as a `CreateIndex` flagged + // `vector`; the `OPTIONS(...)` trailer lands in `options`. + let sql = + "CREATE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE', dimension = 4)"; + match verified_stmt(sql) { + Statement::CreateIndex(CreateIndex { + name, + table_name, + using, + columns, + vector, + or_replace, + unique, + if_not_exists, + with, + options, + .. + }) => { + assert!(vector); + assert!(!or_replace); + assert!(!unique); + assert!(!if_not_exists); + assert_eq!(name.unwrap().to_string(), "emb"); + assert_eq!(table_name.to_string(), "t"); + assert_eq!(using, None); + assert!(with.is_empty()); + assert_eq!( + columns.iter().map(|c| c.to_string()).collect::>(), + vec!["embedding"] + ); + assert_eq!( + options, + vec![ + SqlOption::KeyValue { + key: Ident::new("distance_type"), + value: Expr::Value( + Value::SingleQuotedString("COSINE".to_string()).with_empty_span() + ), + }, + SqlOption::KeyValue { + key: Ident::new("dimension"), + value: Expr::value(number("4")), + }, + ] + ); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + + // `OR REPLACE`. + match verified_stmt( + "CREATE OR REPLACE VECTOR INDEX emb ON t(embedding) OPTIONS(distance_type = 'COSINE')", + ) { + Statement::CreateIndex(CreateIndex { + vector, or_replace, .. + }) => { + assert!(vector); + assert!(or_replace); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + + // `IF NOT EXISTS` and schema-qualified names. + match verified_stmt( + "CREATE VECTOR INDEX IF NOT EXISTS s.emb ON s.t(embedding) OPTIONS(distance_type = 'EUCLIDEAN')", + ) { + Statement::CreateIndex(CreateIndex { + vector, + if_not_exists, + .. + }) => { + assert!(vector); + assert!(if_not_exists); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + + // The bare core, an `INCLUDE` covering-column trailer, and an expression + // target all round-trip. + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding)"); + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) INCLUDE (a, b)"); + verified_stmt("CREATE VECTOR INDEX emb ON t(VEC_COSINE_DISTANCE(embedding))"); +} + #[test] fn parse_drop_index() { let sql = "DROP INDEX idx_a"; From 4e8eacce7e801dd3b47e26e839aeee602e7bd152 Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Tue, 18 Aug 2026 17:26:49 -0700 Subject: [PATCH 3/4] Cover more `CREATE VECTOR INDEX` dialect variants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the shared `CREATE VECTOR INDEX` parsing to the trailers used by the other dialects, and add per-dialect tests: - `STORING(...)` covering-column clause (BigQuery); new `storing` field on `CreateIndex`, rendered after `INCLUDE`. - Accept a `WITH (...)` options clause on a vector index in every dialect (SQL Server `WITH (METRIC = ..., TYPE = ..., MAXDOP = ...)`), not only the dialects that enable it for a plain `CREATE INDEX`. Tests: - `tests/sqlparser_common.rs` — the generic core plus the shared trailers (`INCLUDE`, `STORING`, `WITH`, `USING`, expression targets) across all dialects. - `tests/sqlparser_bigquery.rs` — `OPTIONS(...)` with index_type / distance_type / JSON tuning keys, and `STORING(...)`. - `tests/sqlparser_mssql.rs` — bracket-quoted names with `WITH (...)`. - `tests/sqlparser_mysql.rs` — TiDB's distance-function target with `USING`. - `tests/sqlparser_oracle.rs` — the core, an expression target and an `INCLUDE` list (Oracle's `ORGANIZATION` / `DISTANCE` / `WITH TARGET ACCURACY` / `PARAMETERS` clauses are not yet parsed). Co-authored-by: Claude Opus 4.8 --- src/ast/ddl.rs | 5 +++++ src/ast/spans.rs | 2 ++ src/keywords.rs | 1 + src/parser/mod.rs | 15 ++++++++++++++- tests/sqlparser_bigquery.rs | 12 ++++++++++++ tests/sqlparser_common.rs | 12 +++++++++--- tests/sqlparser_mssql.rs | 9 +++++++++ tests/sqlparser_mysql.rs | 9 +++++++++ tests/sqlparser_oracle.rs | 10 ++++++++++ tests/sqlparser_postgres.rs | 11 +++++++++++ 10 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 9d3be807cd..1f59983c2a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2911,6 +2911,8 @@ pub struct CreateIndex { pub if_not_exists: bool, /// INCLUDE clause: pub include: Vec, + /// `STORING(...)` clause (covering columns on a `CREATE VECTOR INDEX`) + pub storing: Vec, /// NULLS DISTINCT / NOT DISTINCT clause: pub nulls_distinct: Option, /// WITH clause: @@ -2961,6 +2963,9 @@ impl fmt::Display for CreateIndex { if !self.include.is_empty() { write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?; } + if !self.storing.is_empty() { + write!(f, " STORING({})", display_comma_separated(&self.storing))?; + } if let Some(value) = self.nulls_distinct { if value { write!(f, " NULLS DISTINCT")?; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index c8759ab27b..a0149a4d31 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -754,6 +754,7 @@ impl Spanned for CreateIndex { r#async: _, // bool if_not_exists: _, // bool include, + storing, nulls_distinct: _, // bool with, options, @@ -768,6 +769,7 @@ impl Spanned for CreateIndex { .chain(core::iter::once(table_name.span())) .chain(columns.iter().map(|i| i.column.span())) .chain(include.iter().map(|i| i.span)) + .chain(storing.iter().map(|i| i.span)) .chain(with.iter().map(|i| i.span())) .chain(options.iter().map(|i| i.span())) .chain(predicate.iter().map(|i| i.span())) diff --git a/src/keywords.rs b/src/keywords.rs index b35644befb..da85af13e8 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -1039,6 +1039,7 @@ define_keywords!( STORAGE_INTEGRATION, STORAGE_SERIALIZATION_POLICY, STORED, + STORING, STRAIGHT_JOIN, STREAM, STRICT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8c0e816e64..0f4ec3b53f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8888,6 +8888,16 @@ impl<'a> Parser<'a> { vec![] }; + // `STORING(...)` covering columns (e.g. `CREATE VECTOR INDEX`). + let storing = if self.parse_keyword(Keyword::STORING) { + self.expect_token(&Token::LParen)?; + let columns = self.parse_comma_separated(|p| p.parse_identifier())?; + self.expect_token(&Token::RParen)?; + columns + } else { + vec![] + }; + let nulls_distinct = if self.parse_keyword(Keyword::NULLS) { let not = self.parse_keyword(Keyword::NOT); self.expect_keyword_is(Keyword::DISTINCT)?; @@ -8896,7 +8906,9 @@ impl<'a> Parser<'a> { None }; - let with = if self.dialect.supports_create_index_with_clause() + // A vector index accepts a `WITH (...)` options clause in every dialect + // (e.g. SQL Server `WITH (METRIC = ..., TYPE = ...)`). + let with = if (self.dialect.supports_create_index_with_clause() || vector) && self.parse_keyword(Keyword::WITH) { self.expect_token(&Token::LParen)?; @@ -8944,6 +8956,7 @@ impl<'a> Parser<'a> { r#async, if_not_exists, include, + storing, nulls_distinct, with, options, diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index b110423370..12ada52764 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -3001,3 +3001,15 @@ fn test_byte_and_raw_string_quote_escaping() { err ); } + +#[test] +fn parse_bigquery_create_vector_index() { + // BigQuery's form: an `OPTIONS(...)` clause with its index_type / distance_type + // / JSON tuning keys, and a `STORING(...)` covering-column list. + bigquery().verified_stmt( + "CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding) OPTIONS(index_type = 'IVF', distance_type = 'COSINE', ivf_options = '{\"num_lists\": 2500}')", + ); + bigquery().verified_stmt( + "CREATE OR REPLACE VECTOR INDEX my_index ON my_dataset.my_table(embedding) STORING(type, creation_time) OPTIONS(index_type = 'TREE_AH', distance_type = 'EUCLIDEAN')", + ); +} diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 751a3feec4..8ddf2a63c2 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -10446,6 +10446,7 @@ fn test_create_index_with_using_function() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq!("idx_name", name.to_string()); assert_eq!("test", table_name.to_string()); @@ -10506,6 +10507,7 @@ fn test_create_index_with_with_clause() { vector: _, or_replace: _, options: _, + storing: _, }) => { pretty_assertions::assert_eq!("title_idx", name.to_string()); pretty_assertions::assert_eq!("films", table_name.to_string()); @@ -10659,11 +10661,15 @@ fn parse_create_vector_index() { other => panic!("expected CreateIndex, got {other:?}"), } - // The bare core, an `INCLUDE` covering-column trailer, and an expression - // target all round-trip. + // The bare core plus the shared trailers all round-trip across dialects: an + // expression target, `INCLUDE` / `STORING` covering columns, a `WITH` + // options clause, and a trailing `USING `. verified_stmt("CREATE VECTOR INDEX emb ON t(embedding)"); - verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) INCLUDE (a, b)"); verified_stmt("CREATE VECTOR INDEX emb ON t(VEC_COSINE_DISTANCE(embedding))"); + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) INCLUDE (a, b)"); + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) STORING(a, b)"); + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) WITH (metric = 'cosine')"); + verified_stmt("CREATE VECTOR INDEX emb ON t(embedding) USING HNSW"); } #[test] diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index fe8531cace..deaa4f5062 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -2967,3 +2967,12 @@ fn parse_quoted_statement_keyword_aliases() { ms().verified_stmt("SELECT * FROM t [PRINT]"); ms().verified_stmt("SELECT * FROM t AS [PRINT]"); } + +#[test] +fn parse_mssql_create_vector_index() { + // SQL Server's form: bracket-quoted names and a `WITH (...)` options clause + // (`METRIC` / `TYPE` / `MAXDOP`). + ms().verified_stmt( + "CREATE VECTOR INDEX vec_idx ON [dbo].[articles]([title_vector]) WITH (METRIC = 'cosine', TYPE = 'DiskANN', MAXDOP = 8)", + ); +} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index a7aaef6b48..87ef94faf8 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -5245,3 +5245,12 @@ fn parse_key_constraint_index_type_before_columns() { ); } } + +#[test] +fn parse_mysql_create_vector_index() { + // TiDB's form: the vector column is wrapped in a distance function and the + // algorithm is named with a trailing `USING`. + mysql().verified_stmt( + "CREATE VECTOR INDEX idx_cos ON tidb_vectors((VEC_COSINE_DISTANCE(embedding))) USING HNSW", + ); +} diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs index 0ed573be68..73d0090a60 100644 --- a/tests/sqlparser_oracle.rs +++ b/tests/sqlparser_oracle.rs @@ -545,3 +545,13 @@ fn parse_non_reserved_keyword_table_names() { oracle().verified_stmt("SELECT * FROM limit"); oracle().verified_stmt("SELECT * FROM t JOIN limit ON TRUE"); } + +#[test] +fn parse_oracle_create_vector_index() { + // Oracle's specialized clauses (ORGANIZATION / DISTANCE / WITH TARGET + // ACCURACY / PARAMETERS) are not yet parsed; the forms it shares with the + // common grammar — an expression target and an `INCLUDE` list — round-trip. + oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(embedding)"); + oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(VEC_DISTANCE(embedding))"); + oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(embedding) INCLUDE (id)"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 59c9457a04..5a0fe5e5d5 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3297,6 +3297,7 @@ fn parse_create_index() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3337,6 +3338,7 @@ fn parse_create_anonymous_index() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq!(None, name); assert_eq_vec(&["my_table"], &table_name); @@ -3460,6 +3462,7 @@ fn parse_create_indices_with_operator_classes() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3492,6 +3495,7 @@ fn parse_create_indices_with_operator_classes() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3579,6 +3583,7 @@ fn parse_create_bloom() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["bloomidx"], &name); assert_eq_vec(&["tbloom"], &table_name); @@ -3639,6 +3644,7 @@ fn parse_create_brin() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["brin_sensor_data_recorded_at"], &name); assert_eq_vec(&["sensor_data"], &table_name); @@ -3710,6 +3716,7 @@ fn parse_create_index_concurrently() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3750,6 +3757,7 @@ fn parse_create_index_with_predicate() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3790,6 +3798,7 @@ fn parse_create_index_with_include() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3830,6 +3839,7 @@ fn parse_create_index_with_nulls_distinct() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3868,6 +3878,7 @@ fn parse_create_index_with_nulls_distinct() { vector: _, or_replace: _, options: _, + storing: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); From cd6a0a865e54c6a0b76bc1d7729f770084a5b944 Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Thu, 29 Oct 2026 16:42:46 -0700 Subject: [PATCH 4/4] Address review: VECTOR keyword, SQL Server filegroup, drop Oracle tests - Add `VECTOR` as a keyword and match it with `parse_keyword`. - Parse SQL Server's trailing `ON ` on a vector index into a new `CreateIndex::filegroup` field. - Drop the Oracle tests: Oracle's mandatory clauses are still unsupported, and the common test covers the shared prefix. Co-authored-by: Claude Opus 5.5 --- src/ast/ddl.rs | 5 +++++ src/ast/spans.rs | 2 ++ src/keywords.rs | 1 + src/parser/mod.rs | 15 +++++++++------ tests/sqlparser_common.rs | 2 ++ tests/sqlparser_mssql.rs | 13 +++++++++++-- tests/sqlparser_oracle.rs | 10 ---------- tests/sqlparser_postgres.rs | 11 +++++++++++ 8 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1f59983c2a..3c0070d8bc 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2917,6 +2917,8 @@ pub struct CreateIndex { pub nulls_distinct: Option, /// WITH clause: pub with: Vec, + /// `ON ` clause: + pub filegroup: Option, /// `OPTIONS(...)` clause, e.g. on `CREATE VECTOR INDEX` pub options: Vec, /// WHERE clause: @@ -2976,6 +2978,9 @@ impl fmt::Display for CreateIndex { if !self.with.is_empty() { write!(f, " WITH ({})", display_comma_separated(&self.with))?; } + if let Some(filegroup) = &self.filegroup { + write!(f, " ON {filegroup}")?; + } if !self.options.is_empty() { write!(f, " OPTIONS({})", display_comma_separated(&self.options))?; } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index a0149a4d31..ac221879c5 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -757,6 +757,7 @@ impl Spanned for CreateIndex { storing, nulls_distinct: _, // bool with, + filegroup, options, predicate, index_options: _, @@ -771,6 +772,7 @@ impl Spanned for CreateIndex { .chain(include.iter().map(|i| i.span)) .chain(storing.iter().map(|i| i.span)) .chain(with.iter().map(|i| i.span())) + .chain(filegroup.iter().map(|i| i.span)) .chain(options.iter().map(|i| i.span())) .chain(predicate.iter().map(|i| i.span())) .chain(alter_options.iter().map(|i| i.span())), diff --git a/src/keywords.rs b/src/keywords.rs index da85af13e8..dd21991062 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -1187,6 +1187,7 @@ define_keywords!( VARYING, VAR_POP, VAR_SAMP, + VECTOR, VERBOSE, VERSION, VERSIONING, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0f4ec3b53f..0d5f64b639 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -5565,12 +5565,7 @@ impl<'a> Parser<'a> { self.parse_create_schema(or_replace) } else if self.parse_keyword(Keyword::WAREHOUSE) { self.parse_create_warehouse(or_replace).map(Into::into) - } else if matches!( - &self.peek_token_ref().token, - Token::Word(w) if w.keyword == Keyword::NoKeyword && w.value.eq_ignore_ascii_case("VECTOR") - ) { - // `CREATE [OR REPLACE] VECTOR INDEX ...`; VECTOR is not a keyword. - self.next_token(); + } else if self.parse_keyword(Keyword::VECTOR) { self.expect_keyword_is(Keyword::INDEX)?; self.parse_create_index_inner(false, true, or_replace) .map(Into::into) @@ -8919,6 +8914,13 @@ impl<'a> Parser<'a> { Vec::new() }; + // SQL Server places a vector index on a filegroup: `ON [PRIMARY]`. + let filegroup = if vector && self.parse_keyword(Keyword::ON) { + Some(self.parse_identifier()?) + } else { + None + }; + // `OPTIONS(...)` clause (e.g. `CREATE VECTOR INDEX`); no-op when absent. let options = self.parse_options(Keyword::OPTIONS)?; @@ -8959,6 +8961,7 @@ impl<'a> Parser<'a> { storing, nulls_distinct, with, + filegroup, options, predicate, index_options, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 8ddf2a63c2..0b5c395311 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -10447,6 +10447,7 @@ fn test_create_index_with_using_function() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq!("idx_name", name.to_string()); assert_eq!("test", table_name.to_string()); @@ -10508,6 +10509,7 @@ fn test_create_index_with_with_clause() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { pretty_assertions::assert_eq!("title_idx", name.to_string()); pretty_assertions::assert_eq!("films", table_name.to_string()); diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index deaa4f5062..8d99cec986 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -2970,9 +2970,18 @@ fn parse_quoted_statement_keyword_aliases() { #[test] fn parse_mssql_create_vector_index() { - // SQL Server's form: bracket-quoted names and a `WITH (...)` options clause - // (`METRIC` / `TYPE` / `MAXDOP`). + // SQL Server's form: bracket-quoted names, a `WITH (...)` options clause + // (`METRIC` / `TYPE` / `MAXDOP`) and an `ON ` clause. ms().verified_stmt( "CREATE VECTOR INDEX vec_idx ON [dbo].[articles]([title_vector]) WITH (METRIC = 'cosine', TYPE = 'DiskANN', MAXDOP = 8)", ); + match ms().verified_stmt( + "CREATE VECTOR INDEX vec_idx ON [dbo].[articles]([title_vector]) WITH (METRIC = 'cosine', TYPE = 'DiskANN', MAXDOP = 8) ON [PRIMARY]", + ) { + Statement::CreateIndex(CreateIndex { filegroup, .. }) => { + assert_eq!(filegroup, Some(Ident::with_quote('[', "PRIMARY"))); + } + other => panic!("expected CreateIndex, got {other:?}"), + } + ms().verified_stmt(r#"CREATE VECTOR INDEX vec_idx ON articles(title_vector) ON "default""#); } diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs index 73d0090a60..0ed573be68 100644 --- a/tests/sqlparser_oracle.rs +++ b/tests/sqlparser_oracle.rs @@ -545,13 +545,3 @@ fn parse_non_reserved_keyword_table_names() { oracle().verified_stmt("SELECT * FROM limit"); oracle().verified_stmt("SELECT * FROM t JOIN limit ON TRUE"); } - -#[test] -fn parse_oracle_create_vector_index() { - // Oracle's specialized clauses (ORGANIZATION / DISTANCE / WITH TARGET - // ACCURACY / PARAMETERS) are not yet parsed; the forms it shares with the - // common grammar — an expression target and an `INCLUDE` list — round-trip. - oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(embedding)"); - oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(VEC_DISTANCE(embedding))"); - oracle().verified_stmt("CREATE VECTOR INDEX g_idx ON galaxies(embedding) INCLUDE (id)"); -} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 5a0fe5e5d5..141b3ee9c3 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3298,6 +3298,7 @@ fn parse_create_index() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3339,6 +3340,7 @@ fn parse_create_anonymous_index() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq!(None, name); assert_eq_vec(&["my_table"], &table_name); @@ -3463,6 +3465,7 @@ fn parse_create_indices_with_operator_classes() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3496,6 +3499,7 @@ fn parse_create_indices_with_operator_classes() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["the_index_name"], &name); assert_eq_vec(&["users"], &table_name); @@ -3584,6 +3588,7 @@ fn parse_create_bloom() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["bloomidx"], &name); assert_eq_vec(&["tbloom"], &table_name); @@ -3645,6 +3650,7 @@ fn parse_create_brin() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["brin_sensor_data_recorded_at"], &name); assert_eq_vec(&["sensor_data"], &table_name); @@ -3717,6 +3723,7 @@ fn parse_create_index_concurrently() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3758,6 +3765,7 @@ fn parse_create_index_with_predicate() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3799,6 +3807,7 @@ fn parse_create_index_with_include() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3840,6 +3849,7 @@ fn parse_create_index_with_nulls_distinct() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name); @@ -3879,6 +3889,7 @@ fn parse_create_index_with_nulls_distinct() { or_replace: _, options: _, storing: _, + filegroup: _, }) => { assert_eq_vec(&["my_index"], &name); assert_eq_vec(&["my_table"], &table_name);