DBIO-ClickHouse

 view release on metacpan or  search on metacpan

lib/DBIO/ClickHouse/SQLMaker.pm  view on Meta::CPAN

package DBIO::ClickHouse::SQLMaker;
# ABSTRACT: ClickHouse-specific SQL generation for DBIO

use strict;
use warnings;

our $VERSION = '0.900004';

use base qw( DBIO::SQLMaker );


# ClickHouse has no SELECT ... FOR UPDATE (no row locks, no transactions).
sub _lock_select () { '' }


sub new {
  my $self = shift;
  my %opts = (ref $_[0] eq 'HASH') ? %{$_[0]} : @_;

  # exists(), not truthiness: an explicit undef means "emit no SETTINGS clause"
  # and must survive, and 0 is a legal value.
  $opts{mutations_sync} = 1 unless exists $opts{mutations_sync};

  $self->throw_exception(sprintf
    q{Invalid mutations_sync value '%s': ClickHouse accepts 0 (queue the }
  . 'mutation and return), 1 (wait for it on this server) or 2 (wait for it on '
  . 'all replicas). Pass undef to emit no SETTINGS clause and leave the setting '
  . 'to the session or the server profile',
    $opts{mutations_sync}
  ) if defined $opts{mutations_sync} and $opts{mutations_sync} !~ /\A[012]\z/;

  # ClickHouse spells the assignment list without a SET keyword, which is what
  # SQL::Abstract's generic clause renderer would prepend.
  $opts{render_clause}{'update.set'} ||= '_render_update_clause_set';

  return $self->next::method(\%opts);
}


sub update {
  my ($self, $table, $set, $where, $options) = @_;

  $self->_assert_mutation_target(update => $table);

  $self->throw_exception(
    'An UPDATE on ClickHouse renders as ALTER TABLE ... UPDATE col = ..., '
  . 'which has no form without an assignment: update() needs a non-empty '
  . 'hashref of columns to set'
  ) unless ref $set eq 'HASH' and keys %$set;

  my ($sql, @bind) = $self->next::method(
    $table, $set, $self->_mutation_where($where), $options
  );
  $sql .= $self->_mutations_sync_clause;

  return wantarray ? ($sql, @bind) : $sql;
}


sub delete {
  my ($self, $table, $where, $options) = @_;

  $self->_assert_mutation_target(delete => $table);

  my ($sql, @bind) = $self->next::method(
    $table, $self->_mutation_where($where), $options
  );
  $sql .= $self->_mutations_sync_clause;

  return wantarray ? ($sql, @bind) : $sql;
}

# ALTER TABLE names exactly one real table. Everything else core can hand a
# storage as an "ident" -- a from-list, a virtual view's literal SQL -- has no
# ALTER form at all, and would otherwise reach the server as nonsense.
sub _assert_mutation_target {
  my ($self, $op, $table) = @_;

  return unless my $ref = ref $table;

  $self->throw_exception(sprintf
    'ClickHouse renders %s as ALTER TABLE ... %s, which can only name a single '
  . 'real table -- but this one was given a %s target (a virtual view or other '
  . 'literal-SQL result source, or a join list). Run the %s against the '
  . 'underlying MergeTree table instead; if the rows are identified through '
  . 'another table, keep the target table as the result source and put the '
  . 'other one in the WHERE clause as a subquery, e.g. '
  . q{$rs->search({ id => { -in => $other_rs->get_column('id')->as_query } })},
    uc($op), uc($op), $ref, $op
  );
}

# A ClickHouse mutation must carry a filter expression: 'ALTER TABLE t DELETE'
# is a syntax error, not a delete-everything. WHERE 1 is how ClickHouse spells
# what an unrestricted $rs->delete means everywhere else.
sub _mutation_where {
  my ($self, $where) = @_;

  return \'1' unless defined $where;



( run in 1.909 second using v1.01-cache-2.11-cpan-145b269f2f3 )