App-Sqitch
view release on metacpan or search on metacpan
lib/App/Sqitch/Engine/clickhouse.pm view on Meta::CPAN
package App::Sqitch::Engine::clickhouse;
use 5.010;
use strict;
use warnings;
use utf8;
use Try::Tiny;
use App::Sqitch::X qw(hurl);
use Locale::TextDomain qw(App-Sqitch);
use App::Sqitch::Plan::Change;
use Path::Class;
use Scalar::Util qw(looks_like_number);
use Moo;
use App::Sqitch::Types qw(DBH URIDB ArrayRef Str HashRef);
use namespace::autoclean;
use List::MoreUtils qw(firstidx);
extends 'App::Sqitch::Engine';
our $VERSION = 'v1.6.1'; # VERSION
has uri => (
is => 'ro',
isa => URIDB,
lazy => 1,
default => \&_setup_uri,
);
sub _setup_uri {
my $self = shift;
my $uri = $self->SUPER::uri;
my $cfg = $self->_clickcnf;
if (!$uri->host && (my $host = $ENV{CLICKHOUSE_HOST} || $cfg->{host})) {
$uri->host($host);
}
if (!$uri->dbname && (my $db = $cfg->{database})) {
$uri->dbname($db);
}
# Use HTTPS port if CLI using native TLS port.
# https://clickhouse.com/docs/guides/sre/network-ports
$uri->port(8443) if !$uri->_port && ($cfg->{port} || 0) == 9440;
# Always require secure connections when required.
# https://github.com/ClickHouse/ClickHouse/blob/faf6d05/src/Client/ConnectionParameters.cpp#L27-L43
if (
$cfg->{secure}
|| ($cfg->{port} || 0) == 9440 # assume both native and http should be secure or not.
|| ($uri->host || '') =~ /\.clickhouse(?:-staging)?\.cloud\z/
) {
$uri->query_param( SSLMode => 'require' )
unless $uri->query_param( 'SSLMode' );
}
# Add ODBC params for TLS configs.
# https://clickhouse.com/docs/operations/server-configuration-parameters/settings
# https://github.com/clickHouse/clickhouse-odbc?tab=readme-ov-file#configuration
if ( my $tls = $cfg->{tls} ) {
for my $map (
[ privateKeyFile => 'PrivateKeyFile' ],
[ certificateFile => 'CertificateFile' ],
[ caConfig => 'CALocation' ],
) {
if ( my $val = $tls->{ $map->[0] } ) {
if ( my $p = $uri->query_param( $map->[1] ) ) {
# Ideally the ODBC param would override the config,
# bug there is currently no way to pass TLS options to
# the CLI.
hurl engine => __x(
'Client config {cfg_key} value "{cfg_val}" conflicts with ODBC param {odb_param} value "{odbc_val}"',
cfg_key => "openSSL.client.$map->[0]",
cfg_val => $val,
lib/App/Sqitch/Engine/clickhouse.pm view on Meta::CPAN
$uri->query_param( SSLMode => 'allow' );
}
}
}
return $uri;
}
has registry_uri => (
is => 'ro',
isa => URIDB,
lazy => 1,
default => sub {
my $self = shift;
my $uri = $self->uri->clone;
$uri->dbname($self->registry);
return $uri;
},
);
sub registry_destination {
my $uri = shift->registry_uri;
if ($uri->password) {
$uri = $uri->clone;
$uri->password(undef);
}
return $uri->as_string;
}
sub _load_xml {
my $path = shift;
require XML::Tiny;
my $doc = XML::Tiny::parsefile($path->stringify);
return {} unless @{ $doc } > 0;
return _xml2hash($doc->[0]);
}
sub _xml2hash {
my $e = shift;
my $n = $e->{content};
# Return text if it's a text node.
return $n->[0]{content} if @{ $n } == 1 && $n->[0]{type} eq 't';
my $hash = {};
for my $c (@{ $n }) {
# We only care about element nodes.
next if $c->{type} ne 'e';
if (my $prev = $hash->{ $c->{name} }) {
# Convert to an array.
$hash->{ $c->{name} } = $prev = [$prev] unless ref $prev eq 'ARRAY';
push @{ $prev } => _xml2hash($c)
} else {
$hash->{ $c->{name} } = _xml2hash($c);
}
}
return $hash;
}
sub _is_true($) {
my $val = shift || return 0;
# https://github.com/ClickHouse/ClickHouse/blob/ce5a43c/base/poco/Util/src/AbstractConfiguration.cpp#L528C29-L547
return $val != 0 || 0 if looks_like_number $val;
$val = lc $val;
return $val eq 'true' || $val eq 'yes' || $val eq 'on' || 0;
}
# Connection name defaults to host name from url, or else hostname from config
# or else localhost. Then look for that name in a connection under
# `connections_credentials`. If it exists, copy/overwrite `hostname`, `port`,
# `secure`, `user`, `password`, and `database`. Fall back on root object
# values `host` (not `hostname`) `port`, `secure`, `user`, `password`, and
# `database`.
#
# https://github.com/ClickHouse/ClickHouse/blob/d0facf0/programs/client/Client.cpp#L139-L212
sub _conn_cfg {
my ($cfg, $host) = @_;
# Copy root-level configs.
my $conn = {
(exists $cfg->{secure} ? (secure => _is_true $cfg->{secure}) : ()),
map { ( $_ => $cfg->{$_}) } grep { $cfg->{$_} } qw(host port user password database),
};
# Copy client TLS config if exists.
if (my $tls = $cfg->{openSSL}) {
$conn->{tls} = $tls->{client} if $tls->{client};
}
# Copy connection credentials for this host if they exists.
$host ||= $cfg->{host} || 'localhost';
my $creds = $cfg->{connections_credentials} or return $conn;
my $conns = $creds->{connection} or return $conn;
for my $c (@{ ref $conns eq 'ARRAY' ? $conns : [$conns] }) {
next unless ($c->{name} || '') eq $host;
if (exists $c->{secure}) {
$conn->{secure} = _is_true $c->{secure}
}
$conn->{host} = $c->{hostname} if $c->{hostname};
$conn->{$_} = $c->{$_} for grep { $c->{$_} } qw(port user password database);
}
return $conn;
}
has _clickcnf => (
is => 'rw',
isa => HashRef,
lazy => 1,
default => \&_load_cfg,
);
sub _load_cfg {
my $self = shift;
# https://clickhouse.com/docs/interfaces/cli#configuration_files
# https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/Config/getClientConfigPath.cpp
for my $spec (
['.', 'clickhouse-client'],
[App::Sqitch::Config->home_dir, '.clickhouse-client'],
['etc', 'clickhouse-client'],
) {
for my $ext (qw(xml yaml yml)) {
my $path = file $spec->[0], "$spec->[1].$ext";
next unless -f $path;
( run in 0.799 second using v1.01-cache-2.11-cpan-99c4e6809bf )