Back to Documentations

Signature Description Public Member Functions
template<typename S, typename DF>
class   StreamWriter;
This is not a DataFrame memeber function. It is the write-side counterpart to ChunkedReader. It lets a caller build up an output incrementally, one chunk (a small DataFrame sharing the same schema every time) at a time, instead of needing the entire result in memory before calling DataFrame::write() once. This is useful for an ETL-style pipeline: read a huge source in chunks via ChunkedReader, transform each chunk, and write each transformed chunk out via StreamWriter, all in bounded memory throughout.

NOTE: Only io_format::csv2 is currently supported.
StreamWriter Header File

S: A stream type
DF: A DataFrame type
static void test_streamed_write()  {

    std::cout << "\nTesting test_streamed_write( ) ..." << std::endl;

    using MyDataFrame = StdDataFrame<unsigned long>;

    auto    make_slice =
        [](const MyDataFrame &src,
           std::size_t begin,
           std::size_t count) -> MyDataFrame  {

            MyDataFrame out;
            const auto  &src_idx { src.get_index() };
            const auto  &src_a { src.get_column<double>("col_a") };
            const auto  &src_b { src.get_column<int>("col_b") };
            const auto  &src_c { src.get_column<std::string>("col_c") };

            std::vector<unsigned long>  idx;
            std::vector<double>         a;
            std::vector<int>            b;
            std::vector<std::string>    c;

            for (std::size_t i { begin };
                 i < (begin + count) && i < src_idx.size(); ++i) {
                idx.push_back(src_idx[i]);
                a.push_back(src_a[i]);
                b.push_back(src_b[i]);
                c.push_back(src_c[i]);
            }
            out.load_data(std::move(idx),
                          std::make_pair("col_a", a),
                          std::make_pair("col_b", b),
                          std::make_pair("col_c", c));
            return (out);
        };

    const std::size_t           n { 10037 };  // not a multiple of chunk_size
    std::vector<unsigned long>  idx(n);
    std::vector<double>         col_a(n);
    std::vector<int>            col_b(n);
    std::vector<std::string>    col_c(n);

    for (std::size_t i { 0 }; i < n; ++i)  {
        idx[i] = i;
        col_a[i] = double(i) * 1.5;
        col_b[i] = int(i) * 2;
        col_c[i] = "row_" + std::to_string(i);
    }

    MyDataFrame source;

    source.load_data(std::move(idx),
                     std::make_pair("col_a", col_a),
                     std::make_pair("col_b", col_b),
                     std::make_pair("col_c", col_c));

    const char          *out_file { "./stream_writer_test.csv2" };
    const std::size_t   chunk_size = 777;

    // Write the whole thing via StreamWriter, in chunks
    //
    {
        std::ofstream   ostream { out_file };

        assert(ostream.is_open());

        StreamWriter<std::ofstream, MyDataFrame>    writer { ostream, io_format::csv2 };
        std::size_t                                 written { 0 };

        while (written < n)  {
            const std::size_t   this_chunk { std::min(chunk_size, n - written) };
            const MyDataFrame   slice { make_slice(source, written, this_chunk) };
            const bool          wrote { writer.write_chunk<double, int, std::string>(slice) };

            assert(wrote);
            written += this_chunk;
        }
        writer.close();
    }

    // Baseline: what a single-shot write() would have produced
    //
    const char  *baseline_file { "./stream_writer_baseline.csv2" };

    source.write<double, int, std::string>(baseline_file, io_format::csv2);

    // Read both back whole and compare
    //
    MyDataFrame via_streamwriter;
    MyDataFrame via_baseline;

    via_streamwriter.read(out_file, io_format::csv2);
    via_baseline.read(baseline_file, io_format::csv2);

    assert(via_streamwriter.get_index().size() == n);
    assert(via_baseline.get_index().size() == n);

    const auto  &sw_idx { via_streamwriter.get_index() };
    const auto  &sw_a { via_streamwriter.get_column<double>("col_a") };
    const auto  &sw_b { via_streamwriter.get_column<int>("col_b") };
    const auto  &sw_c { via_streamwriter.get_column<std::string>("col_c") };
    const auto  &bl_idx { via_baseline.get_index() };
    const auto  &bl_a { via_baseline.get_column<double>("col_a") };
    const auto  &bl_b { via_baseline.get_column<int>("col_b") };
    const auto  &bl_c { via_baseline.get_column<std::string>("col_c") };

    for (std::size_t i { 0 }; i < n; ++i)  {
        assert(sw_idx[i] == bl_idx[i]);
        assert(sw_a[i] == bl_a[i]);
        assert(sw_b[i] == bl_b[i]);
        assert(sw_c[i] == bl_c[i]);
    }

    // Also read the StreamWriter's output back via ChunkedReader
    //
    {
        std::ifstream                               stream { out_file };
        ChunkedReader<std::ifstream, MyDataFrame>   reader { stream, io_format::csv2 };
        MyDataFrame                                 chunk;
        std::size_t                                 total_rows_seen { 0 };
        std::size_t                                 chunk_count { 0 };

        while (reader.next_chunk(chunk, chunk_size))  {
            chunk_count += 1;

            const auto  &c_idx { chunk.get_index() };
            const auto  &c_a { chunk.get_column<double>("col_a") };
            const auto  &c_b { chunk.get_column<int>("col_b") };
            const auto  &c_c { chunk.get_column<std::string>("col_c") };

            for (std::size_t i { 0 }; i < c_idx.size(); ++i)  {
                const std::size_t   gi { total_rows_seen + i };

                assert(c_idx[i] == bl_idx[gi]);
                assert(c_a[i] == bl_a[gi]);
                assert(c_b[i] == bl_b[gi]);
                assert(c_c[i] == bl_c[gi]);
            }
            total_rows_seen += c_idx.size();
        }
        assert(reader.is_eof());
        assert(total_rows_seen == n);
    }

    // Sanity: file size independent of chunk size used to write it
    //
    {
        const char                                  *out_file2 { "./stream_writer_test_2.csv2" };
        std::ofstream                               ostream2 { out_file2 };
        StreamWriter<std::ofstream, MyDataFrame>    writer2 { ostream2, io_format::csv2 };
        std::size_t                                 written { 0 };

        while (written < n)  {
            const std::size_t   this_chunk { std::min(size_t(37), n - written) };
            const MyDataFrame   slice { make_slice(source, written, this_chunk) };

            writer2.write_chunk<double, int, std::string>(slice);
            written += this_chunk;
        }
        writer2.close();

        MyDataFrame via_tiny;

        via_tiny.read(out_file2, io_format::csv2);
        assert(via_tiny.get_index().size() == n);

        const auto  &t_idx { via_tiny.get_index() };

        for (std::size_t i { 0 }; i < n; ++i)  assert(t_idx[i] == bl_idx[i]);
        std::remove(out_file2);
    }

    // Sanity: unsupported format throws
    //
    {
        bool        threw { false };
        const char  *out_file3 { "./stream_writer_unused.csv2" };

        try  {
            std::ofstream                               ostream3 { out_file3 };
            StreamWriter<std::ofstream, MyDataFrame>    writer3 { ostream3, io_format::binary };
        }
        catch (const NotImplemented &)  { threw = true; }
        assert(threw);
        std::remove(out_file3);
    }

    // Sanity: writing after close() throws
    //
    {
        const char                                  *out_file4 { "./stream_writer_closed_test.csv2" };
        std::ofstream                               ostream4 { out_file4 };
        StreamWriter<std::ofstream, MyDataFrame>    writer4 { ostream4, io_format::csv2 };
        const MyDataFrame                           slice { make_slice(source, 0, 10) };

        writer4.write_chunk<double, int, std::string>(slice);
        writer4.close();

        bool    threw { false };

        try  { writer4.write_chunk<double, int, std::string>(slice); }
        catch (const DataFrameError &)  { threw = true; }
        assert(threw);
        std::remove(out_file4);
    }

    std::remove(baseline_file);
    std::remove(out_file);
}

C++ DataFrame