ClickHouse-Encoder
view release on metacpan or search on metacpan
t/decode-blocks.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;
# decode_blocks reads a concatenated stream of Native blocks - which is
# what a select ... format native response over HTTP returns when the
# result spans multiple blocks (the server emits one block per N rows,
# bounded by max_block_size).
my $enc = ClickHouse::Encoder->new(columns => [['n', 'Int32'], ['s', 'String']]);
# Two blocks of 2 rows each, concatenated.
my $block1 = $enc->encode([[1, "one"], [2, "two"]]);
my $block2 = $enc->encode([[3, "three"], [4, "four"]]);
my $stream = $block1 . $block2;
my $blocks = ClickHouse::Encoder->decode_blocks($stream);
is(scalar(@$blocks), 2, 'two blocks decoded');
is($blocks->[0]{nrows}, 2, 'block 0: 2 rows');
is($blocks->[1]{nrows}, 2, 'block 1: 2 rows');
is_deeply($blocks->[0]{columns}[0]{values}, [1, 2], 'block 0 col 0');
is_deeply($blocks->[1]{columns}[1]{values}, ["three", "four"], 'block 1 col 1');
# Empty stream
{
my $blocks = ClickHouse::Encoder->decode_blocks("");
is_deeply($blocks, [], 'empty stream -> empty list');
}
# Single block also works
{
my $blocks = ClickHouse::Encoder->decode_blocks($block1);
is(scalar(@$blocks), 1, 'single block via decode_blocks');
is($blocks->[0]{nrows}, 2);
}
# Three blocks
{
my $three = $block1 . $block1 . $block2;
my $blocks = ClickHouse::Encoder->decode_blocks($three);
is(scalar(@$blocks), 3, 'three blocks');
is_deeply($blocks->[0]{columns}[0]{values}, [1, 2]);
is_deeply($blocks->[1]{columns}[0]{values}, [1, 2]);
is_deeply($blocks->[2]{columns}[0]{values}, [3, 4]);
}
# Trailing garbage croaks
{
my $err = eval {
ClickHouse::Encoder->decode_blocks($block1 . "xxx"); 1
} ? "" : $@;
like($err, qr/truncated|exceeds remaining/i, 'trailing garbage croaks');
}
# Direct 3-arg form of decode_block (with offset) - this is the XS path
# decode_blocks relies on; pin it independently.
{
my $first = ClickHouse::Encoder->decode_block($stream, 0);
is($first->{nrows}, 2, '3-arg decode_block at offset 0');
my $second = ClickHouse::Encoder->decode_block($stream, $first->{consumed});
is($second->{nrows}, 2, '3-arg decode_block at non-zero offset');
is_deeply($second->{columns}[1]{values}, ["three", "four"],
'3-arg form decoded correct second block');
# Negative offset rejected
my $err = eval { ClickHouse::Encoder->decode_block($stream, -1); 1 }
? "" : $@;
like($err, qr/non-negative/, 'negative offset rejected');
# Offset past end rejected
$err = eval {
ClickHouse::Encoder->decode_block($stream, length($stream) + 1); 1
} ? "" : $@;
like($err, qr/past end/, 'offset past end rejected');
}
# decode_blocks callback form
{
my $three = $block1 . $block1 . $block2;
my @nrows;
ClickHouse::Encoder->decode_blocks($three, sub {
push @nrows, $_[0]{nrows};
});
is_deeply(\@nrows, [2, 2, 2], 'callback form invoked per block');
}
# decode_blocks_iter returns coderef yielding one block per call
{
my $three = $block1 . $block1 . $block2;
my $iter = ClickHouse::Encoder->decode_blocks_iter($three);
isa_ok($iter, 'CODE', 'iter returns coderef');
my @blocks;
while (my $b = $iter->()) { push @blocks, $b }
is(scalar(@blocks), 3, 'iter yielded 3 blocks');
is($iter->(), undef, 'iter exhausted returns undef');
}
# Empty input via iter
{
my $iter = ClickHouse::Encoder->decode_blocks_iter("");
is($iter->(), undef, 'empty input iter -> undef immediately');
}
# decode_stream: pull-style from a filehandle, exercises chunk-by-chunk
# buffer growth and the "need more bytes" retry path.
{
require File::Temp;
my ($fh, $path) = File::Temp::tempfile(UNLINK => 1);
binmode $fh;
print $fh $block1 . $block2;
close $fh;
open my $in, '<', $path or die "open $path: $!";
binmode $in;
my @nrows;
ClickHouse::Encoder->decode_stream($in,
sub { push @nrows, $_[0]{nrows} },
chunk_size => 4); # tiny chunks force the buffer-grow path
close $in;
is_deeply(\@nrows, [2, 2],
'decode_stream yields blocks across small reads');
}
# Truncated tail -> croak with clear message.
( run in 0.516 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )