ClickHouse-Encoder

 view release on metacpan or  search on metacpan

t/decode-projections.t  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use lib 'blib/lib', 'blib/arch';
use ClickHouse::Encoder;

# Column projections: pass a hashref of column names to KEEP; columns
# not in the set still decode (to advance the cursor) but their values
# array is replaced with one undef per row and the column carries a
# `skipped => 1` marker.

my $enc = ClickHouse::Encoder->new(columns => [
    ['id',   'UInt64'],
    ['user', 'String'],
    ['tags', 'Array(String)'],
    ['ts',   'DateTime'],
]);
my $bytes = $enc->encode([
    [1, 'alice', ['a','b'], 1700_000_000],
    [2, 'bob',   ['c'],     1700_000_001],
    [3, 'carol', [],        1700_000_002],
]);

# No filter -> full decode
{
    my $block = ClickHouse::Encoder->decode_block($bytes);
    is($block->{ncols}, 4, 'full decode: ncols');
    is($block->{nrows}, 3, 'full decode: nrows');
    is_deeply($block->{columns}[1]{values}, ['alice','bob','carol'],
              'full decode: user values');
}

# Filter to two columns
{
    my $block = ClickHouse::Encoder->decode_block(
        $bytes, 0, { id => 1, user => 1 });
    is($block->{ncols}, 4, 'filter: ncols still reflects wire shape');
    is($block->{columns}[0]{name}, 'id',   'col 0 name');
    is($block->{columns}[1]{name}, 'user', 'col 1 name');
    is($block->{columns}[2]{name}, 'tags', 'col 2 name');
    is($block->{columns}[3]{name}, 'ts',   'col 3 name');

    ok(!$block->{columns}[0]{skipped}, 'kept col: no skipped marker');
    ok(!$block->{columns}[1]{skipped}, 'kept col: no skipped marker');
    is($block->{columns}[2]{skipped}, 1, 'tags: skipped marker');
    is($block->{columns}[3]{skipped}, 1, 'ts: skipped marker');

    is_deeply($block->{columns}[0]{values}, [1,2,3],
              'kept col values intact');
    is_deeply($block->{columns}[1]{values}, ['alice','bob','carol'],
              'kept col values intact');
    is_deeply($block->{columns}[2]{values}, [undef, undef, undef],
              'skipped col: placeholder undefs');
    is_deeply($block->{columns}[3]{values}, [undef, undef, undef],
              'skipped col: placeholder undefs');
}

# Filter that matches nothing -> all columns skipped, but bytes consumed
{
    my $block = ClickHouse::Encoder->decode_block(
        $bytes, 0, { nonexistent => 1 });
    is_deeply([map $_->{skipped}, @{ $block->{columns} }],
              [1, 1, 1, 1],
              'no matches: every column skipped');
    is($block->{consumed}, length $bytes, 'cursor advanced fully');
}

# Empty filter hashref -> same as no-match
{
    my $block = ClickHouse::Encoder->decode_block($bytes, 0, {});
    is_deeply([map $_->{skipped}, @{ $block->{columns} }],
              [1, 1, 1, 1], 'empty filter: all skipped');
}

# undef filter -> full decode (3rd arg is optional)
{
    my $block = ClickHouse::Encoder->decode_block($bytes, 0, undef);
    is_deeply($block->{columns}[1]{values}, ['alice','bob','carol'],
              'undef filter: full decode');
}

# Non-hashref filter is rejected
{
    my $err = eval {
        ClickHouse::Encoder->decode_block($bytes, 0, ['id']); 1
    } ? '' : $@;
    like($err, qr/columns filter must be a hashref/,
         'arrayref filter rejected');
}

# Plain (non-ref) scalar filter is also rejected, without crashing on
# SvRV(non-ref) inside the error-message format.
{
    my $err = eval {
        ClickHouse::Encoder->decode_block($bytes, 0, "id,user"); 1
    } ? '' : $@;
    like($err, qr/columns filter must be a hashref/,
         'string filter rejected (no segfault)');
}

done_testing();



( run in 2.278 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )