Repository: duckdb/duckdb (language: python)

Issue:
Failed to create prepared statement for insert-statement after called ToString method
### What happens?

`duckdb::Connection` has `Prepare` method accepting `duckdb::SQLStatement`
Calling `Prepare` method after `duckdb::SQLStatement::ToString` led to throwing an exception: 

> GetUsingBinding: expected non-empty binding_name.

it seems to forget reverting `alias` property.
https://github.com/duckdb/duckdb/blob/45559f5eeb1834454a30490fc4ffad1807e13f3b/src/parser/statement/insert_statement.cpp#L99-L103

Is this expected feature?

### To Reproduce

## Code

```cpp
auto db = duckdb::Duckdb(nullptr);
auto conn = duckdb::Connection(db);

// It supporses to exist `Foo` table
// `Foo` table is `CREATE TABLE Foo (id int primary key, name varchar, age int)`
std::string sql("INSERT INTO Foo values (1, 'qwerty', 42)";

// get parsed statements
auto stmts = conn.ExtractStatements(sql);
auto &stmt = stmts[0];

// Call Tostring
stmt->ToString();

// Call prepare
auto prepared_stmt = conn.Prepare(stmt); // <- `duckdb::InternalException` is throws here
```

## Expected result
`duckdb::unique_ptr<duckdb::PreparedStatement>` is returned successfully.

## Actual result
`duckdb::InternalException` is throws
from https://github.com/duckdb/duckdb/blob/45559f5eeb1834454a30490fc4ffad1807e13f3b/src/planner/bind_context.cpp#L100

## Workaround

```cpp
stmt->Copy()->ToString();
auto prepared_stmt = conn.Prepare(stmt);
```

### OS:

MacOS Ventura 13.6.7 (x86_64 16GB RAM)

### DuckDB Version:

1.1.0

### DuckDB Client:

C++

### Hardware:

_No response_

### Full Name:

Kazuhiko TAMURA

### Affiliation:

Free

### 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?

Yes

### 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:

Fix patch:
diff --git a/.github/config/out_of_tree_extensions.cmake b/.github/config/out_of_tree_extensions.cmake
index b162b27a54bb..76d7d141eeec 100644
--- a/.github/config/out_of_tree_extensions.cmake
+++ b/.github/config/out_of_tree_extensions.cmake
@@ -124,6 +124,7 @@ duckdb_extension_load(sqlsmith
         DONT_LINK LOAD_TESTS
         GIT_URL https://github.com/duckdb/duckdb_sqlsmith
         GIT_TAG f24be8b5b0cd0eeed7541e10cff42d7050771afc
+        APPLY_PATCHES
         )
 
 ################# SUBSTRAIT
diff --git a/.github/patches/extensions/sqlsmith/insert_statement_to_string.patch b/.github/patches/extensions/sqlsmith/insert_statement_to_string.patch
new file mode 100644
index 000000000000..30845ddf62ce
--- /dev/null
+++ b/.github/patches/extensions/sqlsmith/insert_statement_to_string.patch
@@ -0,0 +1,13 @@
+diff --git a/test/sql/sql_reduce.test b/test/sql/sql_reduce.test
+index 428ae92..af0530f 100644
+--- a/test/sql/sql_reduce.test
++++ b/test/sql/sql_reduce.test
+@@ -48,7 +48,7 @@ INSERT INTO tbl (VALUES (1))
+ INSERT INTO tbl (VALUES (1, 2))
+ INSERT INTO tbl (VALUES (2))
+ INSERT INTO tbl SELECT *
+-INSERT INTO tbl SELECT NULL FROM (VALUES (1, 2))
++INSERT INTO tbl SELECT NULL FROM (VALUES (1, 2)) AS valueslist
+ INSERT INTO tbl SELECT NULL FROM (VALUES (1, 2)) AS valueslist
+ 
+ query I
diff --git a/src/parser/statement/insert_statement.cpp b/src/parser/statement/insert_statement.cpp
index 4255ebb97b61..4035632975fd 100644
--- a/src/parser/statement/insert_statement.cpp
+++ b/src/parser/statement/insert_statement.cpp
@@ -98,8 +98,10 @@ string InsertStatement::ToString() const {
 	auto values_list = GetValuesList();
 	if (values_list) {
 		D_ASSERT(!default_values);
+		auto saved_alias = values_list->alias;
 		values_list->alias = string();
 		result += values_list->ToString();
+		values_list->alias = saved_alias;
 	} else if (select_statement) {
 		D_ASSERT(!default_values);
 		result += select_statement->ToString();


Test patch:
diff --git a/test/api/test_api.cpp b/test/api/test_api.cpp
index 3452604e4066..8df86cdf9f0b 100644
--- a/test/api/test_api.cpp
+++ b/test/api/test_api.cpp
@@ -580,6 +580,25 @@ TEST_CASE("Issue #4583: Catch Insert/Update/Delete errors", "[api]") {
 	REQUIRE(CHECK_COLUMN(result, 0, {1}));
 }
 
+TEST_CASE("Issue #14130: InsertStatement::ToString causes InternalException later on", "[api][.]") {
+	auto db = DuckDB(nullptr);
+	auto conn = Connection(db);
+
+	conn.Query("CREATE TABLE foo(a int, b varchar, c int)");
+
+	auto query = "INSERT INTO Foo values (1, 'qwerty', 42)";
+
+	auto stmts = conn.ExtractStatements(query);
+	auto &stmt = stmts[0];
+
+	// Issue was here: calling ToString destroyed the 'alias' of the ValuesList
+	stmt->ToString();
+	// Which caused an 'InternalException: expected non-empty binding_name' here
+	auto prepared_stmt = conn.Prepare(std::move(stmt));
+	REQUIRE(!prepared_stmt->HasError());
+	REQUIRE_NO_FAIL(prepared_stmt->Execute());
+}
+
 TEST_CASE("Issue #6284: CachingPhysicalOperator in pull causes issues", "[api][.]") {
 
 	DBConfig config;