Repository: duckdb/duckdb (language: python)

Issue:
An alias of returning clause is dicarded in ToString of UpdateStatement and DeleteStatement
### What happens?

This issue is related to #14523

In both following SQLs:

```
UPDATE Foo SET id = 1 RETURNING id AS updated;
DELETE FROM Foo WHERE id = 1 RETURNING id AS deleted;
```

1. `SQLStatement` is extracted from Connection.
2. Call `SQLStatement::ToString` method.

A SQL discarded alias of returning clause is returned.

But  a RETURNING clause of UPDATE and DELETE statement is not documented  in the current official document.

https://duckdb.org/docs/sql/statements/update#syntax
https://duckdb.org/docs/sql/statements/delete#syntax

So I don't judge it as BUG.
I also want to know whether it is documented the RETURNING clause of UPDATE and DELETE statement in the future version.

### To Reproduce

### UPDATE statement

Code:
```cpp
std::string sql("UPDATE Foo SET id = 1 RETURNING id AS updated");
auto stmts = conn.ExtractStatements(sql);

std::out << stmts[0]->ToString() << std::end;
```

Actual result:
```
UPDATE Foo SET id = 1 RETURNING id
```

Expected result:
```
UPDATE Foo SET id = 1 RETURNING id AS updated
```

### DELETE statement

Code:
```cpp
std::string sql("DELETE FROM Foo WHERE id = 1 RETURNING id AS deleted");
auto stmts = conn.ExtractStatements(sql);

std::out << stmts[0]->ToString() << std::end;
```

Actual result:
```
DELETE FROM Foo WHERE id = 1 RETURNING id
```

Expected result:
```
DELETE FROM Foo WHERE id = 1 RETURNING id AS deleted
```

### OS:

MacOS Ventura 13.6.7 (x86_64 16GB RAM)

### DuckDB Version:

1.1.3

### DuckDB Client:

C++

### Hardware:

_No response_

### Full Name:

Kazuhiko TAMURA

### Affiliation:

have no job

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have not tested with any build

### Did you include all relevant data sets for reproducing the issue?

No - Other reason (please specify in the issue body)

### Did you include all code required to reproduce the issue?

- [X] Yes, I have

### Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?

- [X] Yes, I have


Discussion / hints:
Thanks, this probably needs the same treatment as <https://github.com/duckdb/duckdb/pull/14533>

Fix patch:
diff --git a/src/parser/statement/delete_statement.cpp b/src/parser/statement/delete_statement.cpp
index 1de2767ca27d..0d97068e48bd 100644
--- a/src/parser/statement/delete_statement.cpp
+++ b/src/parser/statement/delete_statement.cpp
@@ -43,7 +43,11 @@ string DeleteStatement::ToString() const {
 			if (i > 0) {
 				result += ", ";
 			}
-			result += returning_list[i]->ToString();
+			auto column = returning_list[i]->ToString();
+			if (!returning_list[i]->alias.empty()) {
+				column += StringUtil::Format(" AS %s", KeywordHelper::WriteOptionallyQuoted(returning_list[i]->alias));
+			}
+			result += column;
 		}
 	}
 	return result;
diff --git a/src/parser/statement/update_statement.cpp b/src/parser/statement/update_statement.cpp
index 6c0c799191f4..835200943ddf 100644
--- a/src/parser/statement/update_statement.cpp
+++ b/src/parser/statement/update_statement.cpp
@@ -65,7 +65,11 @@ string UpdateStatement::ToString() const {
 			if (i > 0) {
 				result += ", ";
 			}
-			result += returning_list[i]->ToString();
+			auto column = returning_list[i]->ToString();
+			if (!returning_list[i]->alias.empty()) {
+				column += StringUtil::Format(" AS %s", KeywordHelper::WriteOptionallyQuoted(returning_list[i]->alias));
+			}
+			result += column;
 		}
 	}
 	return result;


Test patch:
diff --git a/test/api/test_api.cpp b/test/api/test_api.cpp
index 1e7c458f6573..4e9feb707324 100644
--- a/test/api/test_api.cpp
+++ b/test/api/test_api.cpp
@@ -736,3 +736,22 @@ TEST_CASE("Test a logical execute still has types after an optimization pass", "
 	REQUIRE((query_plan->types.size() == 1));
 	REQUIRE((query_plan->types[0].id() == LogicalTypeId::INTEGER));
 }
+
+TEST_CASE("Test SqlStatement::ToString for UPDATE, INSERT, DELETE statements with alias of RETURNING clause", "[api]") {
+	DuckDB db(nullptr);
+	Connection con(db);
+	std::string sql;
+	con.Query("CREATE TABLE test(id INT);");
+
+	sql = "INSERT INTO test (id) VALUES (1) RETURNING id AS inserted";
+	auto stmts = con.ExtractStatements(sql);
+	REQUIRE(stmts[0]->ToString() == "INSERT INTO test (id ) (VALUES (1)) RETURNING id AS inserted");
+
+	sql = "UPDATE test SET id = 1 RETURNING id AS updated";
+	stmts = con.ExtractStatements(sql);
+	REQUIRE(stmts[0]->ToString() == sql);
+
+	sql = "DELETE FROM test WHERE (id = 1) RETURNING id AS deleted";
+	stmts = con.ExtractStatements(sql);
+	REQUIRE(stmts[0]->ToString() == sql);
+}