From ea32affb438032a825b11dc5842def7184cec14d Mon Sep 17 00:00:00 2001 From: ifeanyi Date: Mon, 1 Jun 2026 13:25:30 +0200 Subject: [PATCH] Support multiple targets for `SELECT INTO` Dialects like mysql support ```sql SELECT id, data INTO @x, @y FROM test.t1; ``` but the parser only expects a single table name in the `INTO` clause. This adds support for multiple targets in the `INTO` clause. --- src/ast/query.rs | 16 +++++++++++++--- src/ast/spans.rs | 4 ++-- src/parser/mod.rs | 4 ++-- tests/sqlparser_common.rs | 6 +++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index 1de0e0e9db..eb209228e0 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -3715,8 +3715,11 @@ pub struct SelectInto { pub unlogged: bool, /// `TABLE` keyword present. pub table: bool, - /// Name of the target table. - pub name: ObjectName, + /// Target(s) of the `INTO` clause. + /// + /// [Postgres]: https://www.postgresql.org/docs/current/sql-selectinto.html + /// [MySQL]: https://dev.mysql.com/doc/refman/9.7/en/select-into.html + pub targets: Vec, } impl fmt::Display for SelectInto { @@ -3725,7 +3728,14 @@ impl fmt::Display for SelectInto { let unlogged = if self.unlogged { " UNLOGGED" } else { "" }; let table = if self.table { " TABLE" } else { "" }; - write!(f, "INTO{}{}{} {}", temporary, unlogged, table, self.name) + write!( + f, + "INTO{}{}{} {}", + temporary, + unlogged, + table, + display_comma_separated(&self.targets) + ) } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 0e328db433..360d7707f8 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2392,10 +2392,10 @@ impl Spanned for SelectInto { temporary: _, // bool unlogged: _, // bool table: _, // bool - name, + targets, } = self; - name.span() + union_spans(targets.iter().map(|t| t.span())) } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3c61851930..e0c2dc269b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19627,13 +19627,13 @@ impl<'a> Parser<'a> { .is_some(); let unlogged = self.parse_keyword(Keyword::UNLOGGED); let table = self.parse_keyword(Keyword::TABLE); - let name = self.parse_object_name(false)?; + let targets = self.parse_comma_separated(Parser::parse_expr)?; Ok(SelectInto { temporary, unlogged, table, - name, + targets, }) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index c2d298d04e..d12b6a4441 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -1118,7 +1118,7 @@ fn parse_select_into() { temporary: false, unlogged: false, table: false, - name: ObjectName::from(vec![Ident::new("table0")]), + targets: vec![Expr::Identifier(Ident::new("table0"))], }, only(&select.into) ); @@ -1129,6 +1129,10 @@ fn parse_select_into() { "SELECT * INTO TEMPORARY UNLOGGED TABLE table0 FROM table1", ); + verified_only_select("SELECT a, b INTO foo.bar, bar.baz FROM t"); + verified_stmt("SELECT a, b, c INTO p, q, r FROM t"); + verified_stmt("SELECT a, b INTO :h1, :h2 FROM t"); + // Do not allow aliases here let sql = "SELECT * INTO table0 asdf FROM table1"; let result = parse_sql_statements(sql);