Repository: duckdb/duckdb (language: python)

Issue:
ParsedExpression::Hasparameter returns false in case of replace list has placeholders
### What happens?

In following SQL, `HasParameter method` of `ParsedExpression` (`StarExpression`) returns false:

```sql
select * replace(i * $n as i)
from range(1, 10) t(i)
```

This SQL has a placeholder in replace list.

In current implementation, `ParsedExpression::HasParameter` method uses `ExpressionIterator::EnumerateChildren` method to iterate children.

https://github.com/duckdb/duckdb/blob/10c42435f1805ee4415faa5d6da4943e8c98fa55/src/planner/expression.cpp#L89

`ExpressionIterator::EnumerateChildren` is only captured `StarExpression::expr` field.
So `StarExpression::replace_list` does not capture.

https://github.com/duckdb/duckdb/blob/10c42435f1805ee4415faa5d6da4943e8c98fa55/src/parser/parsed_expression_iterator.cpp#L100

~~And I seems that `StarExpression::exclude_list` is not also captured in case of using lambda expression with placeholder.~~
EDIT: `exclude_list` does not contain `ParsedExpression. So it has no problem.

Is this expected behavior?


### To Reproduce

```cpp
std::string sql = "select * replace(i * $n as i) from range(1, 10) t(i)";
auto stmts = conn.ExtractStatements(sql);

auto& select_stmt = stmts[0]->Cast<duckdb::SelectStatement>();
auto& select_node = select_stmt.node->Cast<duckdb::SelectNode>();

std::cout << select_node.select_list[0]->HasParameter() << std::endl;
```

Expected result: true
Actual result: false


### OS:

MacOS Ventura 13.6.7 (x86_64 16GB RAM)

### DuckDB Version:

1.1.2

### DuckDB Client:

C++

### Hardware:

_No response_

### Full Name:

Kazuhiko TAMURA

### Affiliation:

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:
Looks like it's working as intended
```sql
statement ok
prepare p1 as
select * replace(i * $n as i)
from range(1, 10) t(i)

query I
execute p1(n := 2)
----
2
4
6
8
10
12
14
16
18
```

I'm not sure why HasParameter is not picking this up for you, but the system is properly detecting the parameter so there has to be additional logic involved if HasParameter isn't solely responsible
@Tishj 

Yes, it properly handles placeholders of replace list in CLI.

I've checked source code.
In generating logical plan (prepared statement) stage, the star expression is all expanded including replace-list.

https://github.com/duckdb/duckdb/blob/baa915d95ffa72461f5124dee645e4d4547c316f/src/planner/binder/expression/bind_star_expression.cpp#L149

(Called `BindContext::GenerateAllColumnExpressions` method)

So, returning false from `HasParameter` method will not result in affecting  to the prepared statement.


Fix patch:
diff --git a/src/parser/parsed_expression_iterator.cpp b/src/parser/parsed_expression_iterator.cpp
index 6d807800c206..8b29ed7fe560 100644
--- a/src/parser/parsed_expression_iterator.cpp
+++ b/src/parser/parsed_expression_iterator.cpp
@@ -102,6 +102,9 @@ void ParsedExpressionIterator::EnumerateChildren(
 		if (star_expr.expr) {
 			callback(star_expr.expr);
 		}
+		for (auto &item : star_expr.replace_list) {
+			callback(item.second);
+		}
 		break;
 	}
 	case ExpressionClass::SUBQUERY: {


Test patch:
diff --git a/test/api/test_api.cpp b/test/api/test_api.cpp
index 8df86cdf9f0b..da7136af22bb 100644
--- a/test/api/test_api.cpp
+++ b/test/api/test_api.cpp
@@ -3,6 +3,8 @@
 #include "duckdb/parser/parser.hpp"
 #include "duckdb/planner/logical_operator.hpp"
 #include "duckdb/main/connection_manager.hpp"
+#include "duckdb/parser/statement/select_statement.hpp"
+#include "duckdb/parser/query_node/select_node.hpp"
 
 #include <chrono>
 #include <thread>
@@ -19,6 +21,18 @@ TEST_CASE("Test comment in CPP API", "[api]") {
 	REQUIRE(1);
 }
 
+TEST_CASE("Test StarExpression replace_list parameter", "[api]") {
+	DuckDB db(nullptr);
+	Connection con(db);
+	auto sql = "select * replace(i * $n as i) from range(1, 10) t(i)";
+	auto stmts = con.ExtractStatements(sql);
+
+	auto &select_stmt = stmts[0]->Cast<SelectStatement>();
+	auto &select_node = select_stmt.node->Cast<SelectNode>();
+
+	REQUIRE(select_node.select_list[0]->HasParameter());
+}
+
 TEST_CASE("Test using connection after database is gone", "[api]") {
 	auto db = make_uniq<DuckDB>(nullptr);
 	auto conn = make_uniq<Connection>(*db);