Repository: duckdb/duckdb
File: extension/core_functions/scalar/struct/struct_keys.cpp (C++)

Code before:
#include "duckdb/common/types/vector.hpp"
#include "duckdb/execution/expression_executor_state.hpp"
#include "core_functions/scalar/struct_functions.hpp"

namespace duckdb {

static void StructKeysFunction(DataChunk &args, ExpressionState &state, Vector &result) {
	auto &input = args.data[0];
	const idx_t count = args.size();

	if (input.GetType().id() != LogicalTypeId::STRUCT) {
		throw InvalidInputException("struct_keys() expects a STRUCT argument");
	}

	auto keys = StructVector::GetKeys(input);
	const idx_t key_count = keys.size();
	const auto list_type = LogicalType::LIST(LogicalType::VARCHAR);

	// If the input is a constant during constant folding, we must return a CONSTANT_VECTOR
	if (input.GetVectorType() == VectorType::CONSTANT_VECTOR) {
		if (ConstantVector::IsNull(input)) {
			Vector null_vec(list_type);
			ConstantVector::SetNull(null_vec, true);
			result.Reference(null_vec);
			result.SetVectorType(VectorType::CONSTANT_VECTOR);
			return;
		}

		Vector list_vec(list_type);
		auto &list_child = ListVector::GetEntry(list_vec);
		list_child.SetVectorType(VectorType::FLAT_VECTOR);
		auto child_data = FlatVector::GetData<string_t>(list_child);
		for (idx_t i = 0; i < key_count; i++) {
			child_data[i] = StringVector::AddString(list_child, keys[i]);
		}
		ListVector::SetListSize(list_vec, key_count);
		auto list_entries = FlatVector::GetData<list_entry_t>(list_vec);
		list_entries[0] = {0, key_count};

		result.Reference(list_vec);

Review comment (suggestion):
This is going to be constant for all inputs right? So you could just save this in the bind callback once instead of allocating a new copy of the keys every time you process a new data chunk.

Code after:
#include "duckdb/common/types/vector.hpp"
#include "duckdb/execution/expression_executor_state.hpp"
#include "core_functions/scalar/struct_functions.hpp"
#include "duckdb/planner/expression/bound_function_expression.hpp"

namespace duckdb {

struct StructKeysBindData : public FunctionData {
	const LogicalType type;
	Vector keys_vector;

	explicit StructKeysBindData(const LogicalType &type_p)
	    : type(type_p), keys_vector(LogicalType::LIST(LogicalType::VARCHAR), 2) {
		const auto &child_types = StructType::GetChildTypes(type);
		const auto count = child_types.size();

		ListVector::Reserve(keys_vector, count);
		auto &list_child = ListVector::GetEntry(keys_vector);
		auto child_data = FlatVector::GetData<string_t>(list_child);
		for (idx_t i = 0; i < count; i++) {
			child_data[i] = StringVector::AddString(list_child, child_types[i].first);
		}
		ListVector::SetListSize(keys_vector, count);

		auto list_entries = FlatVector::GetData<list_entry_t>(keys_vector);
		list_entries[0] = {0, count};

		auto &validity = FlatVector::Validity(keys_vector);
		validity.EnsureWritable();
		validity.SetInvalid(1);
	}

	bool Equals(const FunctionData &other) const override {
		auto &o = other.Cast<StructKeysBindData>();
		// Compare type and flag (content is derived from them)
		return type == o.type;
	}

	unique_ptr<FunctionData> Copy() const override {
		return make_uniq<StructKeysBindData>(type);