ClickHouse-Encoder
view release on metacpan or search on metacpan
my $loc = ch_query_result("select loc from test_geo order by code, loc limit 1");
like($loc, qr/\(51\.5,/, 'Point roundtrip');
my $area_count = ch_query_result(
"select count() from test_geo where length(area) >= 1");
is($area_count, '3', 'Polygon roundtrip (3 polygons)');
}
# JSON type (CH 24.8+)
SKIP: {
my $ver = ch_query_result("select version()");
my ($major, $minor) = $ver =~ /^(\d+)\.(\d+)/ ? ($1, $2) : (0, 0);
skip "JSON type requires CH 24.8+", 8
if ($major < 24) || ($major == 24 && $minor < 8);
ch_query("drop table if exists test_json");
# JSON type needs an experimental-feature gate in CH 24.x/25.x and
# an explicit enable in 26.x. Each clickhouse-client call is a new
# session, so the flag must be set on the same call as the CREATE.
# `--allow_experimental_json_type=1 --enable_json_type=1` covers
# both: unknown flags are silently ignored by clickhouse-client.
my @json_flags = ('--allow_experimental_json_type=1',
'--enable_json_type=1');
system(@ch_cmd, @json_flags, '--query',
"create table test_json (j JSON) engine = Memory") == 0
or skip "Could not create JSON table (CH may not support it)", 8;
require JSON::PP;
my $enc = ClickHouse::Encoder->new(columns => [['j', 'JSON']]);
my $rows = [
[{name => "alice", age => 30, active => JSON::PP::true()}],
[{name => "bob", age => 25}],
[{}],
[undef],
[{score => 3.14}],
[{user => {name => "carol", age => 40}}],
];
my $data = $enc->encode($rows);
ok(ch_insert_native('test_json', $data), 'INSERT format native (JSON)');
my $n = ch_query_result("select count() from test_json");
is($n, '6', '6 rows in JSON table');
my $nm_alice = ch_query_result(
"select count() from test_json where j.name = 'alice'");
is($nm_alice, '1', 'string subcolumn query');
my $score_count = ch_query_result(
"select count() from test_json where toFloat64(j.score) > 3");
is($score_count, '1', 'float subcolumn query');
my $carol_age = ch_query_result(
"select toInt64(j.user.age) from test_json where j.user.name = 'carol'");
is($carol_age, '40', 'nested path subcolumn query');
# Read back JSON column via select format native and decode
open my $fh, '-|', @ch_cmd, '--query',
'select j from test_json order by toInt64(j.age) asc nulls last format native'
or die "Cannot spawn clickhouse-client: $!";
binmode $fh;
my $native = do { local $/; <$fh> };
close $fh;
my $block = ClickHouse::Encoder->decode_block($native);
is($block->{ncols}, 1, 'decoded ncols');
is($block->{nrows}, 6, 'decoded nrows');
# All 6 should be hashrefs
is(scalar(grep { ref $_ eq 'HASH' } @{ $block->{columns}[0]{values} }),
6, 'all 6 decoded rows are hashrefs');
# Typed JSON paths
ch_query("drop table if exists test_json_typed");
system(@ch_cmd, @json_flags, '--query',
"create table test_json_typed "
. "(j JSON(name String, age UInt32)) engine = Memory") == 0
or skip "Could not create typed JSON table", 3;
my $te = ClickHouse::Encoder->new(
columns => [['j','JSON(name String, age UInt32)']]);
my $td = $te->encode([
[{name => 'alice', age => 30}],
[{name => 'bob', age => 25, extra => 'more'}],
]);
ok(ch_insert_native('test_json_typed', $td),
'typed JSON paths INSERT accepted');
my $cnt = ch_query_result("select count() from test_json_typed");
is($cnt, '2', 'typed JSON paths: 2 rows');
my $name = ch_query_result(
"select j.name from test_json_typed where j.age = 30");
is($name, 'alice', 'typed path subcolumn query');
}
# Test: compressed Native INSERT over HTTP. End-to-end wire-compat
# check for the bundled CityHash128 v1.0.2 port - if our hash diverges
# from cityhash102 in any detail the server rejects with "Checksum
# doesn't match in compressed block" before storing any rows.
SKIP: {
eval { require HTTP::Tiny; require Compress::LZ4; 1 }
or skip 'HTTP::Tiny or Compress::LZ4 not installed', 4;
my $port = $http_port;
my $http = HTTP::Tiny->new(timeout => 5);
my $ping = $http->get("http://127.0.0.1:$port/ping");
skip "HTTP endpoint not reachable on :$port", 4
unless $ping->{success} && $ping->{content} =~ /Ok/;
# Drop + create a fresh table via the existing client; then push
# rows through the HTTP `?decompress=1` path.
ch_query("drop table if exists test_compressed");
ch_query("create table test_compressed
(id Int32, msg String, ts DateTime) engine = Memory");
my $enc = ClickHouse::Encoder->new(columns =>
[['id','Int32'], ['msg','String'], ['ts','DateTime']]);
my $native = $enc->encode([
[1, 'alpha', 1700000001],
[2, 'beta', 1700000002],
[3, 'gamma', 1700000003],
]);
# Wrap in CH's compressed-block framing - this calls our bundled
# CityHash128 v1.0.2 implementation. If the hash bytes diverge
# from CH's cityhash102 the server will refuse the INSERT.
my $framed = ClickHouse::Encoder->compress_native_block(
$native, mode => 'lz4');
my $resp = $http->post(
"http://127.0.0.1:$port/"
. "?query=insert+into+test_compressed+format+native"
. "&decompress=1",
( run in 0.525 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )