Repository: duckdb/duckdb (language: python)

Issue:
Appender C API does not support generated columns
### What happens?

Inserting using the appender C API does not work:

* Inserting a NULL or default value does not work, raises errors
* Not inserting anything does not work either - errors with number of column mismatch 

### To Reproduce

```cpp
#include "duckdb.h"

int main() {
  duckdb_database db;
  duckdb_connection con;

  if (duckdb_open("db", &db) == DuckDBError)
    throw;
  if (duckdb_connect(db, &con) == DuckDBError)
    throw;

  // works
  // if (duckdb_query(con, "create or replace table tbl (a varchar)", NULL) == DuckDBError)
  // does not work
  if (duckdb_query(con, "create or replace table tbl (a varchar, b varchar generated always as (a))", NULL) == DuckDBError)
    throw;

  duckdb_appender appender;
  if (duckdb_appender_create(con, NULL, "tbl", &appender) == DuckDBError)
    throw;

  // append row
  duckdb_append_varchar(appender, "a");
  // not appending anything at column b does not work
  // duckdb_append_default(appender); // does not work either
  // duckdb_append_varchar(appender, "b"); // does not work either
  if (duckdb_appender_end_row(appender) == DuckDBError)
    throw;

  duckdb_appender_destroy(&appender);
  duckdb_disconnect(&con);
  duckdb_close(&db);

  return 0;
}
```

### OS:

Ubuntu 22.04.4 LTS (x86_64, WSL)

### DuckDB Version:

v1.1.1 af39bd0dcf

### DuckDB Client:

v1.1.1 af39bd0dcf

### Hardware:

_No response_

### Full Name:

German Gambon

### Affiliation:

N/A

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

I have tested with a stable release

### 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/src/include/duckdb/main/table_description.hpp b/src/include/duckdb/main/table_description.hpp
index 2a35b4f16691..151592f49355 100644
--- a/src/include/duckdb/main/table_description.hpp
+++ b/src/include/duckdb/main/table_description.hpp
@@ -13,12 +13,26 @@
 namespace duckdb {
 
 struct TableDescription {
+public:
 	//! The schema of the table
 	string schema;
 	//! The table name of the table
 	string table;
 	//! The columns of the table
 	vector<ColumnDefinition> columns;
+
+public:
+	idx_t PhysicalColumnCount() const {
+		idx_t count = 0;
+		for (auto &column : columns) {
+			if (column.Generated()) {
+				continue;
+			}
+			count++;
+		}
+		D_ASSERT(count != 0);
+		return count;
+	}
 };
 
 } // namespace duckdb
diff --git a/src/main/appender.cpp b/src/main/appender.cpp
index ae3608524149..33ef17bf444c 100644
--- a/src/main/appender.cpp
+++ b/src/main/appender.cpp
@@ -62,6 +62,9 @@ Appender::Appender(Connection &con, const string &schema_name, const string &tab
 	}
 	vector<optional_ptr<const ParsedExpression>> defaults;
 	for (auto &column : description->columns) {
+		if (column.Generated()) {
+			continue;
+		}
 		types.push_back(column.Type());
 		defaults.push_back(column.HasDefaultValue() ? &column.DefaultValue() : nullptr);
 	}
diff --git a/src/main/client_context.cpp b/src/main/client_context.cpp
index 467f28dacde1..90c029ab498b 100644
--- a/src/main/client_context.cpp
+++ b/src/main/client_context.cpp
@@ -1132,13 +1132,19 @@ void ClientContext::Append(TableDescription &description, ColumnDataCollection &
 		auto &table_entry =
 		    Catalog::GetEntry<TableCatalogEntry>(*this, INVALID_CATALOG, description.schema, description.table);
 		// verify that the table columns and types match up
-		if (description.columns.size() != table_entry.GetColumns().PhysicalColumnCount()) {
+		if (description.PhysicalColumnCount() != table_entry.GetColumns().PhysicalColumnCount()) {
 			throw InvalidInputException("Failed to append: table entry has different number of columns!");
 		}
+		idx_t table_entry_col_idx = 0;
 		for (idx_t i = 0; i < description.columns.size(); i++) {
-			if (description.columns[i].Type() != table_entry.GetColumns().GetColumn(PhysicalIndex(i)).Type()) {
+			auto &column = description.columns[i];
+			if (column.Generated()) {
+				continue;
+			}
+			if (column.Type() != table_entry.GetColumns().GetColumn(PhysicalIndex(table_entry_col_idx)).Type()) {
 				throw InvalidInputException("Failed to append: table entry has different number of columns!");
 			}
+			table_entry_col_idx++;
 		}
 		auto binder = Binder::CreateBinder(*this);
 		auto bound_constraints = binder->BindConstraints(table_entry);


Test patch:
diff --git a/test/appender/test_appender.cpp b/test/appender/test_appender.cpp
index bb659982e2c5..e20d498c8833 100644
--- a/test/appender/test_appender.cpp
+++ b/test/appender/test_appender.cpp
@@ -169,6 +169,69 @@ TEST_CASE("Test AppendRow", "[appender]") {
 	REQUIRE(CHECK_COLUMN(result, 2, {Value::TIMESTAMP(1992, 1, 1, 1, 1, 1, 0)}));
 }
 
+TEST_CASE("Test appender with generated column", "[appender]") {
+	DuckDB db(nullptr); // Create an in-memory DuckDB database
+	Connection con(db); // Create a connection to the database
+
+	SECTION("Insert into table with generated column first") {
+		// Try to create a table with a generated column
+		REQUIRE_NOTHROW(con.Query(R"(
+			CREATE TABLE tbl (
+				b VARCHAR GENERATED ALWAYS AS (a),
+				a VARCHAR
+			)
+		)"));
+
+		Appender appender(con, "tbl");
+		REQUIRE_NOTHROW(appender.BeginRow());
+		REQUIRE_NOTHROW(appender.Append("a"));
+
+		// Column 'b' is generated from 'a', so it does not need to be explicitly appended
+		// End the row
+		REQUIRE_NOTHROW(appender.EndRow());
+
+		// Close the appender
+		REQUIRE_NOTHROW(appender.Close());
+
+		// Query the table to verify that the row was inserted correctly
+		auto result = con.Query("SELECT * FROM tbl");
+		REQUIRE_NO_FAIL(*result);
+
+		// Check that the column 'a' contains "a" and 'b' contains the generated value "a"
+		REQUIRE(CHECK_COLUMN(result, 0, {Value("a")}));
+		REQUIRE(CHECK_COLUMN(result, 1, {Value("a")}));
+	}
+
+	SECTION("Insert into table with generated column second") {
+		// Try to create a table with a generated column
+		REQUIRE_NOTHROW(con.Query(R"(
+			CREATE TABLE tbl (
+				a VARCHAR,
+				b VARCHAR GENERATED ALWAYS AS (a)
+			)
+		)"));
+
+		Appender appender(con, "tbl");
+		REQUIRE_NOTHROW(appender.BeginRow());
+		REQUIRE_NOTHROW(appender.Append("a"));
+
+		// Column 'b' is generated from 'a', so it does not need to be explicitly appended
+		// End the row
+		REQUIRE_NOTHROW(appender.EndRow());
+
+		// Close the appender
+		REQUIRE_NOTHROW(appender.Close());
+
+		// Query the table to verify that the row was inserted correctly
+		auto result = con.Query("SELECT * FROM tbl");
+		REQUIRE_NO_FAIL(*result);
+
+		// Check that the column 'a' contains "a" and 'b' contains the generated value "a"
+		REQUIRE(CHECK_COLUMN(result, 0, {Value("a")}));
+		REQUIRE(CHECK_COLUMN(result, 1, {Value("a")}));
+	}
+}
+
 TEST_CASE("Test default value appender", "[appender]") {
 	duckdb::unique_ptr<QueryResult> result;
 	DuckDB db(nullptr);