```py
## writing ArrowStream file from python
import pyarrow as pa
data = [
    pa.array([1, 2, 3, 4, 5]),
    pa.array(["one", "two", "three", "four", "five"]).dictionary_encode(),
    pa.array([1, 2, 3, 4, 5]).dictionary_encode(),
    pa.array([True, False, True, True, True])
]
batch = pa.record_batch(data, names=['id', 'lc_nullable', 'lc_int_nullable', 'bool_nullable'])
writer = pa.ipc.new_stream("test4.arrows", batch.schema)
writer.write_batch(batch)
writer.close()
```

```
clickhouse-local --query='SELECT * FROM table FORMAT TSVWithNamesAndTypes' --stacktrace --input-format=ArrowStream < test4.arrows
id	lc_nullable	lc_int_nullable	bool_nullable
Nullable(Int64)	LowCardinality(Nullable(String))	LowCardinality(Nullable(Int64))	Nullable(UInt8)
1		0	1
2	one	1	0
3	two	2	1
4	three	3	1
5	four	4	1
```

0 in LowCardianality dictionary in ClickHouse have special meaning (null/default), while in Arrow it's a regular values.

Probably remapping needed... :|
arrow::DictionaryArray & dict_chunk = dynamic_cast<arrow::DictionaryArray &>(*(arrow_column->chunk(chunk_i)));
                    dict_array.emplace_back(dict_chunk.dictionary());
                }
                auto arrow_dict_column = std::make_shared<arrow::ChunkedArray>(dict_array);
                auto dict_column = readColumnFromArrowColumn(arrow_dict_column, column_name, format_name, false, dictionary_values, read_ints_as_dates, allow_null_type, skip_columns_with_unsupported_types, skipped);
                
                auto tmp_lc_column = DataTypeLowCardinality(dict_column.type).createColumn();
                auto tmp_dict_column = IColumn::mutate(assert_cast<ColumnLowCardinality *>(tmp_lc_column.get())->getDictionaryPtr());
                static_cast<IColumnUnique *>(tmp_dict_column.get())->uniqueInsertRangeFrom(*dict_column.column, 0, dict_column.column->size());
                dict_column.column = std::move(tmp_dict_column);
                dict_values = std::make_shared<ColumnWithTypeAndName>(std::move(dict_column));
            }
            arrow::ArrayVector indexes_array;
            for (size_t chunk_i = 0, num_chunks = static_cast<size_t>(arrow_column->num_chunks()); chunk_i < num_chunks; ++chunk_i)
            {
                arrow::DictionaryArray & dict_chunk = dynamic_cast<arrow::DictionaryArray &>(*(arrow_column->chunk(chunk_i)));
                indexes_array.emplace_back(dict_chunk.indices());
            }
            auto arrow_indexes_column = std::make_shared<arrow::ChunkedArray>(indexes_array);
            auto indexes_column = readColumnWithIndexesData(arrow_indexes_column);
            auto lc_column = ColumnLowCardinality::create(dict_values->column, indexes_column);
            auto lc_type = std::make_shared<DataTypeLowCardinality>(dict_values->type);
            return {std::move(lc_column), std::move(lc_type), column_name};
        }
#    define DISPATCH(ARROW_NUMERIC_TYPE, CPP_NUMERIC_TYPE) \
        case ARROW_NUMERIC_TYPE: \
            return readColumnWithNumericData<CPP_NUMERIC_TYPE>(arrow_column, column_name);
        FOR_ARROW_NUMERIC_TYPES(DISPATCH)
#    undef DISPATCH