Repository: duckdb/duckdb (language: python)

Issue:
"Failed to bind column reference" when inserting into a table with a virtual column
### What happens?

When inserting into a table with a virtual column duckdb throws error and blocks further interactions with the table

![Screenshot from 2024-08-19 18-51-34](https://github.com/user-attachments/assets/01ef18cc-bb9f-4190-8133-434c420f0432)


### To Reproduce

### How to reproduce in cli

```sql
CREATE TABLE t1 (a CHAR NOT NULL, c CHAR GENERATED ALWAYS AS (a) VIRTUAL, b INT);
CREATE UNIQUE INDEX t1_idx ON t1 (a);
INSERT OR REPLACE INTO t1 (a, b) VALUES ('a', 1);
```

The output after executing last line from the above:
```
INTERNAL Error: Failed to bind column reference "b" [10.2] (bindings: {#[10.0], #[10.1], #[2.0], #[2.1]})
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
For more information, see https://duckdb.org/docs/dev/internal_errors
```

### Observations
#### There is no issue if virtual column is last in table so below runs without issue
``` 
CREATE TABLE t1 (a CHAR NOT NULL, b INT, c CHAR GENERATED ALWAYS AS (a) VIRTUAL);
CREATE UNIQUE INDEX t1_idx ON t1 (a);
INSERT OR REPLACE INTO t1 (a, b) VALUES ('a', 1);
```
#### There is no issue if index is not created so below runs ok
```
CREATE TABLE t1 (a CHAR NOT NULL, b INT, c CHAR GENERATED ALWAYS AS (a) VIRTUAL);
INSERT INTO t1 (a, b) VALUES ('a', 1);
```
#### Issue is also reproduced when inserting BY NAME
```
CREATE TABLE t1 (a CHAR NOT NULL, c CHAR GENERATED ALWAYS AS (a) VIRTUAL, b INT);
CREATE UNIQUE INDEX t1_idx ON t1 (a);
INSERT OR REPLACE INTO t1 BY NAME (SELECT 'a' as a, 1 as b);
```

### OS:

Linux, x86_64

### DuckDB Version:

1.0.0 and also nightly build e1568a2

### DuckDB Client:

cli

### Full Name:

Boris Urman

### Affiliation:

Logility

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a nightly 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:
Thanks, I could reproduce this. This error should not occur - we'll take a look.
I have tried to fix the issue.
before:
```shell
D select * from t1;
┌─────────┬─────────┬───────┐
│    a    │    c    │   b   │
│ varchar │ varchar │ int32 │
├─────────┴─────────┴───────┤
│          0 rows           │
└───────────────────────────┘
D INSERT OR REPLACE INTO t1 (a, b) VALUES ('a', 1);
INTERNAL Error: Failed to bind column reference "b" [10.2] (bindings: {#[10.0], #[10.1], #[2.0], #[2.1]})
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
For more information, see https://duckdb.org/docs/dev/internal_errors
D 

```

and after:
```shell
Enter ".help" for usage hints.
D INSERT OR REPLACE INTO t1 (a, b) VALUES ('a', 1);
D select * from t1;
┌─────────┬─────────┬───────┐
│    a    │    c    │   b   │
│ varchar │ varchar │ int32 │
├─────────┼─────────┼───────┤
│ a       │ a       │     1 │
└─────────┴─────────┴───────┘
D 
```

Fix patch:
diff --git a/src/planner/binder/statement/bind_insert.cpp b/src/planner/binder/statement/bind_insert.cpp
index ac430edee321..f02be80009d1 100644
--- a/src/planner/binder/statement/bind_insert.cpp
+++ b/src/planner/binder/statement/bind_insert.cpp
@@ -295,7 +295,7 @@ void Binder::BindOnConflictClause(LogicalInsert &insert, TableCatalogEntry &tabl
 			auto entry = specified_columns.find(col.Name());
 			if (entry != specified_columns.end()) {
 				// column was specified, set to the index
-				insert.on_conflict_filter.insert(col.Oid());
+				insert.on_conflict_filter.insert(col.Physical().index);
 			}
 		}
 		bool index_references_columns = false;
@@ -353,8 +353,12 @@ void Binder::BindOnConflictClause(LogicalInsert &insert, TableCatalogEntry &tabl
 	// add a bind context entry for it
 	auto excluded_index = GenerateTableIndex();
 	insert.excluded_table_index = excluded_index;
-	auto table_column_names = columns.GetColumnNames();
-	auto table_column_types = columns.GetColumnTypes();
+	vector<string> table_column_names;
+	vector<LogicalType> table_column_types;
+	for (auto &col : columns.Physical()) {
+		table_column_names.push_back(col.Name());
+		table_column_types.push_back(col.Type());
+	}
 	bind_context.AddGenericBinding(excluded_index, "excluded", table_column_names, table_column_types);
 
 	if (on_conflict.condition) {


Test patch:
diff --git a/test/sql/upsert/test_generated_column.test b/test/sql/upsert/test_generated_column.test
new file mode 100644
index 000000000000..00281a2d2a28
--- /dev/null
+++ b/test/sql/upsert/test_generated_column.test
@@ -0,0 +1,48 @@
+# name: test/sql/upsert/test_generated_column.test
+# group: [upsert]
+
+# SET expression targets b (located after the virtual column)
+
+statement ok
+CREATE TABLE t1 (
+	a CHAR NOT NULL,
+	c CHAR GENERATED ALWAYS AS (a) VIRTUAL,
+	b INT,
+);
+
+statement ok
+CREATE UNIQUE INDEX t1_idx ON t1 (a);
+
+statement ok
+INSERT INTO t1 VALUES ('a', 1) ON CONFLICT(a) DO UPDATE SET b = excluded.b;
+
+statement ok
+INSERT INTO t1 VALUES ('a', 1) ON CONFLICT(a) DO UPDATE SET b = excluded.b;
+
+query III
+select * from t1;
+----
+a	a	1
+
+# The ON CONFLICT (a) is logically located after the virtual column
+
+statement ok
+CREATE TABLE t2 (
+	b INT,
+	c CHAR GENERATED ALWAYS AS (a) VIRTUAL,
+	a CHAR NOT NULL,
+);
+
+statement ok
+CREATE UNIQUE INDEX t2_idx ON t2 (a);
+
+statement ok
+INSERT INTO t2 VALUES (1, 'a') ON CONFLICT(a) DO UPDATE SET b = excluded.b;
+
+statement ok
+INSERT INTO t2 VALUES (1, 'a') ON CONFLICT(a) DO UPDATE SET b = excluded.b;
+
+query III
+select * from t1;
+----
+a	a	1