Repository: duckdb/duckdb
File: tools/pythonpkg/src/arrow_array_stream.cpp (C++)

Code before:

	py::object scanner;
	py::object arrow_scanner = scanner_class.attr("from_dataset");
	if (py::isinstance(arrow_obj_handle, table_class)) {
		auto arrow_dataset = py::module_::import("pyarrow.dataset").attr("dataset");
		auto dataset = arrow_dataset(arrow_obj_handle);
		scanner = ProduceScanner(arrow_scanner, dataset, parameters, factory->config);
	} else if (py::isinstance(arrow_obj_handle, record_batch_reader_class)) {
		py::object arrow_batch_scanner = py::module_::import("pyarrow.dataset").attr("Scanner").attr("from_batches");
		scanner = ProduceScanner(arrow_batch_scanner, arrow_obj_handle, parameters, factory->config);
	} else if (py::isinstance(arrow_obj_handle, scanner_class)) {
		// If it's a scanner we have to turn it to a record batch reader, and then a scanner again since we can't stack
		// scanners on arrow Otherwise pushed-down projections and filters will disappear like tears in the rain
		auto record_batches = arrow_obj_handle.attr("to_reader")();
		py::object arrow_batch_scanner = py::module_::import("pyarrow.dataset").attr("Scanner").attr("from_batches");
		scanner = ProduceScanner(arrow_batch_scanner, record_batches, parameters, factory->config);
	} else {
		scanner = ProduceScanner(arrow_scanner, arrow_obj_handle, parameters, factory->config);
	}

	auto record_batches = scanner.attr("to_reader")();
	auto res = make_unique<ArrowArrayStreamWrapper>();
	auto export_to_c = record_batches.attr("_export_to_c");
	export_to_c((uint64_t)&res->arrow_array_stream);
	return res;
}

void PythonTableArrowArrayStreamFactory::GetSchema(uintptr_t factory_ptr, ArrowSchemaWrapper &schema) {
	py::gil_scoped_acquire acquire;
	PythonTableArrowArrayStreamFactory *factory = (PythonTableArrowArrayStreamFactory *)factory_ptr;
	D_ASSERT(factory->arrow_object);
	auto scanner_class = py::module::import("pyarrow.dataset").attr("Scanner");
	py::handle arrow_obj_handle(factory->arrow_object);
	if (py::isinstance(arrow_obj_handle, scanner_class)) {
		auto obj_schema = arrow_obj_handle.attr("projected_schema");
		auto export_to_c = obj_schema.attr("_export_to_c");
		export_to_c((uint64_t)&schema);
	} else {
		auto obj_schema = arrow_obj_handle.attr("schema");
		auto export_to_c = obj_schema.attr("_export_to_c");
		export_to_c((uint64_t)&schema);
	}
}

py::object GetScalar(Value &constant, const string &timezone_config) {
	py::object scalar = py::module_::import("pyarrow").attr("scalar");
	py::object dataset_scalar = py::module_::import("pyarrow.dataset").attr("scalar");
	py::object scalar_value;
	switch (constant.type().id()) {
	case LogicalTypeId::BOOLEAN:
		return dataset_scalar(constant.GetValue<bool>());

Review comment (suggestion):
Can this be a GetArrowType method that returns an enum, and turn these into a switch case?

I think an enum here would be nice  since it could also be used on other APIs that quack arrow

Code after:
	auto &column_list = parameters.projected_columns.columns;
	bool has_filter = filters && !filters->filters.empty();
	py::list projection_list = py::cast(column_list);
	if (has_filter) {
		auto filter = TransformFilter(*filters, parameters.projected_columns.projection_map, config);
		if (column_list.empty()) {
			return arrow_scanner(arrow_obj_handle, py::arg("filter") = filter);
		} else {
			return arrow_scanner(arrow_obj_handle, py::arg("columns") = projection_list, py::arg("filter") = filter);
		}
	} else {
		if (column_list.empty()) {
			return arrow_scanner(arrow_obj_handle);
		} else {
			return arrow_scanner(arrow_obj_handle, py::arg("columns") = projection_list);
		}
	}
}
unique_ptr<ArrowArrayStreamWrapper> PythonTableArrowArrayStreamFactory::Produce(uintptr_t factory_ptr,
                                                                                ArrowStreamParameters &parameters) {
	py::gil_scoped_acquire acquire;
	PythonTableArrowArrayStreamFactory *factory = (PythonTableArrowArrayStreamFactory *)factory_ptr;
	D_ASSERT(factory->arrow_object);
	py::handle arrow_obj_handle(factory->arrow_object);
	auto arrow_object_type = GetArrowType(arrow_obj_handle);
	auto scanner_class = py::module::import("pyarrow.dataset").attr("Scanner");

	py::object scanner;
	py::object arrow_scanner = scanner_class.attr("from_dataset");
	py::object arrow_batch_scanner = py::module_::import("pyarrow.dataset").attr("Scanner").attr("from_batches");
	switch (arrow_object_type) {
	case PyArrowObjectType::Table: {
		auto arrow_dataset = py::module_::import("pyarrow.dataset").attr("dataset");
		auto dataset = arrow_dataset(arrow_obj_handle);
		scanner = ProduceScanner(arrow_scanner, dataset, parameters, factory->config);
		break;
	}
	case PyArrowObjectType::RecordBatchReader: {
		scanner = ProduceScanner(arrow_batch_scanner, arrow_obj_handle, parameters, factory->config);
		break;
	}
	case PyArrowObjectType::Scanner: {
		// If it's a scanner we have to turn it to a record batch reader, and then a scanner again since we can't stack
		// scanners on arrow Otherwise pushed-down projections and filters will disappear like tears in the rain
		auto record_batches = arrow_obj_handle.attr("to_reader")();
		scanner = ProduceScanner(arrow_batch_scanner, record_batches, parameters, factory->config);
		break;
	}
	case PyArrowObjectType::InMemoryDataset:
	case PyArrowObjectType::FileSystemDataset: {
		scanner = ProduceScanner(arrow_scanner, arrow_obj_handle, parameters, factory->config);