PostgreSQL: support INCLUDE on PRIMARY KEY / UNIQUE table constraints (#2366)
diff --git a/src/ast/table_constraints.rs b/src/ast/table_constraints.rs
index 463bf43..0db71bb 100644
--- a/src/ast/table_constraints.rs
+++ b/src/ast/table_constraints.rs
@@ -456,6 +456,8 @@
     pub index_type: Option<IndexType>,
     /// Identifiers of the columns that form the primary key.
     pub columns: Vec<IndexColumn>,
+    /// INCLUDE clause: <https://www.postgresql.org/docs/current/sql-createtable.html>
+    pub include: Vec<Ident>,
     /// Optional index options such as `USING`.
     pub index_options: Vec<IndexOption>,
     /// Optional characteristics like `DEFERRABLE`.
@@ -474,6 +476,10 @@
             display_comma_separated(&self.columns),
         )?;
 
+        if !self.include.is_empty() {
+            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
+        }
+
         if !self.index_options.is_empty() {
             write!(f, " {}", display_separated(&self.index_options, " "))?;
         }
@@ -495,6 +501,7 @@
                 .map(|i| i.span)
                 .chain(self.index_name.iter().map(|i| i.span))
                 .chain(self.columns.iter().map(|i| i.span()))
+                .chain(self.include.iter().map(|i| i.span))
                 .chain(self.characteristics.iter().map(|i| i.span())),
         )
     }
@@ -519,6 +526,8 @@
     pub index_type: Option<IndexType>,
     /// Identifiers of the columns that are unique.
     pub columns: Vec<IndexColumn>,
+    /// INCLUDE clause: <https://www.postgresql.org/docs/current/sql-createtable.html>
+    pub include: Vec<Ident>,
     /// Optional index options such as `USING`.
     pub index_options: Vec<IndexOption>,
     /// Optional characteristics like `DEFERRABLE`.
@@ -541,6 +550,10 @@
             display_comma_separated(&self.columns),
         )?;
 
+        if !self.include.is_empty() {
+            write!(f, " INCLUDE ({})", display_comma_separated(&self.include))?;
+        }
+
         if !self.index_options.is_empty() {
             write!(f, " {}", display_separated(&self.index_options, " "))?;
         }
@@ -562,6 +575,7 @@
                 .map(|i| i.span)
                 .chain(self.index_name.iter().map(|i| i.span))
                 .chain(self.columns.iter().map(|i| i.span()))
+                .chain(self.include.iter().map(|i| i.span))
                 .chain(self.characteristics.iter().map(|i| i.span())),
         )
     }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 74199c2..2d51d47 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9581,6 +9581,7 @@
                     index_name: None,
                     index_type: None,
                     columns: vec![],
+                    include: vec![],
                     index_options: vec![],
                     characteristics,
                 }
@@ -9601,6 +9602,7 @@
                     index_type_display,
                     index_type: None,
                     columns: vec![],
+                    include: vec![],
                     index_options: vec![],
                     characteristics,
                     nulls_distinct: NullsDistinctOption::None,
@@ -9617,6 +9619,7 @@
                     index_name: None,
                     index_type: None,
                     columns: vec![],
+                    include: vec![],
                     index_options: vec![],
                     characteristics,
                 }
@@ -10044,6 +10047,7 @@
                 let index_type = self.parse_optional_using_then_index_type()?;
 
                 let columns = self.parse_parenthesized_index_column_list()?;
+                let include = self.parse_optional_include_columns()?;
                 let index_options = self.parse_index_options()?;
                 let characteristics = self.parse_constraint_characteristics()?;
                 Ok(Some(
@@ -10053,6 +10057,7 @@
                         index_type_display,
                         index_type,
                         columns,
+                        include,
                         index_options,
                         characteristics,
                         nulls_distinct,
@@ -10077,6 +10082,7 @@
                 let index_type = self.parse_optional_using_then_index_type()?;
 
                 let columns = self.parse_parenthesized_index_column_list()?;
+                let include = self.parse_optional_include_columns()?;
                 let index_options = self.parse_index_options()?;
                 let characteristics = self.parse_constraint_characteristics()?;
                 Ok(Some(
@@ -10085,6 +10091,7 @@
                         index_name,
                         index_type,
                         columns,
+                        include,
                         index_options,
                         characteristics,
                     }
@@ -10469,6 +10476,18 @@
         }
     }
 
+    /// Parse an optional `INCLUDE (col, ...)` clause on a table constraint.
+    pub fn parse_optional_include_columns(&mut self) -> Result<Vec<Ident>, ParserError> {
+        if self.parse_keyword(Keyword::INCLUDE) {
+            self.expect_token(&Token::LParen)?;
+            let columns = self.parse_comma_separated(|p| p.parse_identifier())?;
+            self.expect_token(&Token::RParen)?;
+            Ok(columns)
+        } else {
+            Ok(vec![])
+        }
+    }
+
     /// Parse a single `SqlOption` used by various dialect-specific DDL statements.
     pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
         let is_mssql = dialect_of!(self is MsSqlDialect|GenericDialect);
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 97f5a04..bad0836 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -3955,6 +3955,7 @@
                                     index_name: None,
                                     index_type: None,
                                     columns: vec![],
+                                    include: vec![],
                                     index_options: vec![],
                                     characteristics: None,
                                 }),
@@ -3971,6 +3972,7 @@
                                     index_type_display: KeyOrIndexDisplay::None,
                                     index_type: None,
                                     columns: vec![],
+                                    include: vec![],
                                     index_options: vec![],
                                     characteristics: None,
                                     nulls_distinct: NullsDistinctOption::None,
@@ -4309,6 +4311,7 @@
                                 index_type_display: KeyOrIndexDisplay::None,
                                 index_type: None,
                                 columns: vec![],
+                                include: vec![],
                                 index_options: vec![],
                                 characteristics: expected_value,
                                 nulls_distinct: NullsDistinctOption::None,
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index d5b3a74..797a125 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -655,6 +655,7 @@
                                 index_name: None,
                                 index_type: None,
                                 columns: vec![],
+                                include: vec![],
                                 index_options: vec![],
                                 characteristics: None,
                             }),
@@ -706,6 +707,7 @@
             index_type_display,
             index_type,
             columns,
+            include: vec![],
             index_options,
             characteristics,
             nulls_distinct: NullsDistinctOption::None,
@@ -716,6 +718,7 @@
             index_name,
             index_type,
             columns,
+            include: vec![],
             index_options,
             characteristics,
         }
@@ -764,6 +767,7 @@
                                         index_name: None,
                                         index_type: None,
                                         columns: vec![],
+                                        include: vec![],
                                         index_options: vec![],
                                         characteristics: None,
                                     }),
@@ -1436,6 +1440,7 @@
                             index_name: None,
                             index_type: None,
                             columns: vec![],
+                            include: vec![],
                             index_options: vec![],
                             characteristics: None,
                         }),
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 73cb9ec..a7128ea 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -792,6 +792,50 @@
 }
 
 #[test]
+fn parse_constraint_include_columns() {
+    // INCLUDE covering columns on PRIMARY KEY / UNIQUE table constraints.
+    // https://www.postgresql.org/docs/current/sql-createtable.html
+    pg_and_generic().verified_stmt(
+        "CREATE TABLE t (id INT, payload TEXT, CONSTRAINT t_pk PRIMARY KEY (id) INCLUDE (payload))",
+    );
+    pg_and_generic().verified_stmt(
+        "CREATE TABLE t (id INT, email TEXT, payload TEXT, CONSTRAINT t_uk UNIQUE (email) INCLUDE (payload))",
+    );
+    pg_and_generic().verified_stmt(
+        "CREATE TABLE t (a INT, b INT, c INT, d INT, CONSTRAINT t_pk PRIMARY KEY (a, b) INCLUDE (c, d))",
+    );
+    pg_and_generic()
+        .verified_stmt("ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id) INCLUDE (payload)");
+    pg_and_generic()
+        .verified_stmt("ALTER TABLE t ADD CONSTRAINT t_uk UNIQUE (email) INCLUDE (payload)");
+    pg_and_generic().verified_stmt(
+        "ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id) INCLUDE (payload) DEFERRABLE INITIALLY DEFERRED",
+    );
+
+    match pg_and_generic().verified_stmt(
+        "ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id) INCLUDE (payload, extra)",
+    ) {
+        Statement::AlterTable(alter_table) => match &alter_table.operations[0] {
+            AlterTableOperation::AddConstraint {
+                constraint: TableConstraint::PrimaryKey(pk),
+                ..
+            } => {
+                assert_eq!(pk.name.as_ref().unwrap().to_string(), "t_pk");
+                assert_eq!(
+                    pk.include
+                        .iter()
+                        .map(|i| i.value.clone())
+                        .collect::<Vec<_>>(),
+                    vec!["payload".to_string(), "extra".to_string()]
+                );
+            }
+            _ => unreachable!(),
+        },
+        _ => unreachable!(),
+    }
+}
+
+#[test]
 fn parse_alter_table_disable() {
     pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");
     pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE RULE rule_name");
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 70c73bf..b72d294 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -223,6 +223,7 @@
                                 index_name: None,
                                 index_type: None,
                                 columns: vec![],
+                                include: vec![],
                                 index_options: vec![],
                                 characteristics: None,
                             }),
@@ -255,6 +256,7 @@
                     index_name: None,
                     index_type: None,
                     columns: vec![],
+                    include: vec![],
                     index_options: vec![],
                     characteristics: None,
                 }),