ClickHouse-Encoder

 view release on metacpan or  search on metacpan

t/live.t  view on Meta::CPAN

    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 )