DBIO

 view release on metacpan or  search on metacpan

lib/DBIO/ResultSet.pm  view on Meta::CPAN

      # if all else fails - get all primary keys and operate over a ORed set
      # wrap in a transaction for consistency
      # this is where the group_by/multiplication starts to matter
      if (
        $existing_group_by
          or
        # we do not need to check pre-multipliers, since if the premulti is there, its
        # parent (who is multi) will be there too
        keys %{ $join_classifications->{multiplying} || {} }
      ) {
        # make sure if there is a supplied group_by it matches the columns compiled above
        # perfectly. Anything else can not be sanely executed on most databases so croak
        # right then and there
        if ($existing_group_by) {
          my @current_group_by = map
            { $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" }
            @$existing_group_by
          ;

          if (
            join ("\x00", sort @current_group_by)
              ne
            join ("\x00", sort @{$attrs->{columns}} )
          ) {
            $self->throw_exception (
              "You have just attempted a $op operation on a resultset which does group_by"
              . ' on columns other than the primary keys, while DBIO internally needs to retrieve'
              . ' the primary keys in a subselect. All sane RDBMS engines do not support this'
              . ' kind of queries. Please retry the operation with a modified group_by or'
              . ' without using one at all.'
            );
          }
        }

        $subrs = $subrs->search({}, { group_by => $attrs->{columns} });
      }

      $guard = $storage->txn_scope_guard;

      for my $row ($subrs->cursor->all) {
        push @$cond, { map
          { $idcols->[$_] => $row->[$_] }
          (0 .. $#$idcols)
        };
      }
    }
  }

  my $res = $cond ? $storage->$op (
    $rsrc,
    $op eq 'update' ? $values : (),
    $cond,
  ) : '0E0';

  $guard->commit if $guard;

  return $res;
}


sub update {
  my ($self, $values, $options) = @_;
  $self->throw_exception('Values for update must be a hash')
    unless ref $values eq 'HASH';
  $self->_assert_restricted_or_all('update', $options);

  # no row is involved: every value goes to the storage as it is
  assert_column_value($_, $values->{$_}) for sort keys %$values;

  return $self->_rs_update_delete ('update', $values);
}


sub update_all {
  my ($self, $values, $options) = @_;
  $self->throw_exception('Values for update_all must be a hash')
    unless ref $values eq 'HASH';
  $self->_assert_restricted_or_all('update_all', $options);

  my $guard = $self->result_source->schema->txn_scope_guard;
  $_->update({%$values}) for $self->all;  # shallow copy - update will mangle it
  $guard->commit;
  return 1;
}


sub delete {
  my ($self, $options) = @_;
  $self->_assert_restricted_or_all('delete', $options);

  return $self->_rs_update_delete ('delete');
}


sub delete_all {
  my ($self, $options) = @_;
  $self->_assert_restricted_or_all('delete_all', $options);

  my $guard = $self->result_source->schema->txn_scope_guard;
  $_->delete for $self->all;
  $guard->commit;
  return 1;
}


sub populate {
  my $self = shift;

  # this is naive and just a quick check
  # the types will need to be checked more thoroughly when the
  # multi-source populate gets added
  my $data = (
    ref $_[0] eq 'ARRAY'
      and
    ( @{$_[0]} or return )
      and
    ( ref $_[0][0] eq 'HASH' or ref $_[0][0] eq 'ARRAY' )
      and
    $_[0]
  ) or $self->throw_exception('Populate expects an arrayref of hashrefs or arrayref of arrayrefs');

  # FIXME - no cref handling
  # At this point assume either hashes or arrays

  if(defined wantarray) {
    my (@results, $guard);

    if (ref $data->[0] eq 'ARRAY') {
      # column names only, nothing to do
      return if @$data == 1;

      $guard = $self->result_source->schema->storage->txn_scope_guard
        if @$data > 2;

lib/DBIO/ResultSet.pm  view on Meta::CPAN

sub find_or_new_async {
  my $self  = shift;
  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $hash  = ref $_[0] eq 'HASH' ? shift : {@_};

  return $self->_rs_run_async('find_or_new',
    sub {
      my ($backend, $fc) = @_;
      return $fc->done($self->new_result($hash)) unless keys %$hash;

      return $self->_find_for_write_async($hash, $attrs, 'find_or_new')->then(sub {
        my ($row) = @_;
        return $row if $row;
        return $self->new_result($hash);
      });
    },
    sub { $self->find_or_new($hash, $attrs) },
  );
}


sub create {
  #my ($self, $col_data) = @_;
  assert_no_internal_indirect_calls and fail_on_internal_call;
  return shift->new_result(shift)->insert;
}


sub find_or_create {
  my $self     = shift;
  my $attrs    = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $hash     = ref $_[0] eq 'HASH' ? shift : {@_};
  if (keys %$hash and my $row = $self->_find_for_write($hash, $attrs, 'find_or_create') ) {
    return $row;
  }
  return $self->new_result($hash)->insert;
}


sub find_or_create_async {
  my $self  = shift;
  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $hash  = ref $_[0] eq 'HASH' ? shift : {@_};

  return $self->_rs_run_async('find_or_create',
    sub {
      my ($backend, $fc) = @_;
      return $self->create_async($hash) unless keys %$hash;

      return $self->_find_for_write_async($hash, $attrs, 'find_or_create')->then(sub {
        my ($row) = @_;
        return $row if $row;
        return $self->create_async($hash);
      });
    },
    sub { $self->find_or_create($hash, $attrs) },
  );
}


sub update_or_create {
  my $self = shift;
  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $cond = ref $_[0] eq 'HASH' ? shift : {@_};

  # find() only accepts a condition it can reduce to concrete columns on this
  # table, so the lookup drops any relation-valued key that does not resolve to
  # a concrete foreign-key condition. The FULL condition -- relation keys
  # included -- still reaches the row layer via update()/insert below, which
  # owns nested-relation semantics.
  my @lookup_cols;
  my $row = $self->_find_for_write($self->_find_lookup_cond($cond), $attrs, 'update_or_create', \@lookup_cols);
  if (defined $row) {
    $row->update($self->_update_data_for_found_row($cond, \@lookup_cols));
  }
  else {
    $row = $self->new_result($cond)->insert;
  }

  return $row;
}

# Build the find() lookup for update_or_create: the passed condition with every
# relation-valued key that does NOT reduce to a concrete foreign-key condition
# on this table removed. A resolvable belongs_to (FK-bearing hashref, or a
# blessed related object) resolves join-free to { self_fk => <defined value> }
# and stays, so find() folds it in as before. A has_many / has_one / might_have
# searched by its own non-key columns is either crosstable, or resolves join-free
# to { self_pk => undef }; leaving it in the lookup would make find() reject it
# (before the guard, it silently clobbered the key with an undef FK). Such keys
# are dropped from the LOOKUP only -- update_or_create still passes the full
# condition to the row layer, which owns nested-relation semantics. See ADR 0034.
sub _find_lookup_cond {
  my ($self, $cond) = @_;

  my $rsrc = $self->result_source;
  my %lookup = %$cond;

  for my $key (keys %lookup) {
    next unless ref $lookup{$key} eq 'HASH';
    next unless $rsrc->relationship_info($key);

    my ($rel_cond, $crosstable)
      = $self->_resolve_related_cond($rsrc, $key, $lookup{$key});

    delete $lookup{$key}
      unless $self->_related_cond_is_concrete($rel_cond, $crosstable);
  }

  return \%lookup;
}


sub update_or_create_async {
  my $self  = shift;
  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $cond  = ref $_[0] eq 'HASH' ? shift : {@_};

  return $self->_rs_run_async('update_or_create',
    sub {
      my ($backend, $fc) = @_;

      my @lookup_cols;
      return $self->_find_for_write_async($self->_find_lookup_cond($cond), $attrs, 'update_or_create', \@lookup_cols)->then(sub {
        my ($row) = @_;
        return $row->update_async($self->_update_data_for_found_row($cond, \@lookup_cols)) if defined $row;
        return $self->create_async($cond);
      });
    },
    sub { $self->update_or_create($cond, $attrs) },
  );
}


my %_upsert_options = map { $_ => 1 } qw(
  key conflict_target on_conflict update update_where result fallback relations
);

my %_upsert_result_shapes = map { $_ => 1 } qw(
  row_or_undef row_or_die count void
);

# What a single entry of the `relations` structure may say. Deliberately just
# the conflict target plus its own nesting: on_conflict, update and result are
# whole-cascade or whole-statement decisions, not per-part ones.
my %_upsert_relation_options = map { $_ => 1 } qw(
  key conflict_target relations
);


sub upsert_async {
  my ($self, $data, $opts) = @_;

  my $rsrc  = $self->result_source;
  my $sname = $rsrc->source_name || $rsrc->result_class || 'this result source';

  return $self->_rs_run_async('upsert',
    sub {
      my ($backend, $fc, $mode, @rest) = @_;

      # Cascade and fallback are each already more than one statement (a
      # transactional multi-part write, and a racy client-side read-then-write
      # respectively) -- not expressible as a single insert_async. Run
      # synchronously and resolve immediately, same precedent as
      # create_async's related-object multi-create (ADR 0031).
      if ($mode eq 'cascade') {
        return $self->_async_ctx_or_sync_punt($fc, 'upsert',
          'a cascade upsert is a transactional multi-part write',
          sub { $self->_upsert_cascade($data, $opts, $rest[0]) });
      }
      if ($mode eq 'fallback') {
        return $self->_async_ctx_or_sync_punt($fc, 'upsert',
          'the fallback upsert is a racy client-side read-then-write',
          sub { $self->_upsert_fallback($data, $opts, $rest[0]) });

lib/DBIO/ResultSet.pm  view on Meta::CPAN

# A row identified entirely by its own conflict target -- the lookup row that
# a belongs_to part almost always is, { name => 'Y' } keyed on name -- leaves
# the conflict branch with nothing to set. Flat, that is a caller mistake and
# upsert says so, pointing at on_conflict => 'nothing'. In a cascade it is not
# a choice the caller can make: DO NOTHING writes no row, and a part something
# hangs off has to be nameable. So the branch re-asserts the target instead --
# col = <the value that was attempted> -- which is a no-op on the value and
# keeps the row returned. Exactly the idiom ADR 0036 section 4 blesses for
# MySQL's identity set, for the same reason.
sub _upsert_node_opts {
  my ($self, $node, $row) = @_;

  my %opts = %{ $node->{opts} };

  return \%opts
    if exists $opts{update}
    or ( $opts{on_conflict} || 'update' ) eq 'nothing'
    or ! @{ $node->{target_cols} };

  my %rowdata = $row->get_columns;
  my %skip = map { $_ => 1 }
    @{ $node->{target_cols} }, $node->{rs}->result_source->primary_columns;

  return \%opts if grep { ! $skip{$_} } keys %rowdata;

  my @noop = grep { exists $rowdata{$_} } @{ $node->{target_cols} };
  $opts{update} = \@noop if @noop;

  return \%opts;
}

# Walk the plan: parts whose key we need first, us, then the parts that need
# ours. $prebuilt is the Row the caller of this node already had to build in
# order to set the foreign key on it.
sub _upsert_run {
  my ($self, $node, $prebuilt) = @_;

  my $rs  = $node->{rs};
  my $row = $prebuilt || $rs->new_result($node->{cols});

  $row->set_from_related(
    $_->{rel}, $self->_upsert_run($_->{node})
  ) for @{ $node->{before} };

  $row = $rs->upsert(
    { $row->get_columns }, $self->_upsert_node_opts($node, $row), $row,
  );

  for my $after (@{ $node->{after} }) {
    for my $child (@{ $after->{nodes} }) {
      my $crow = $child->{rs}->new_result($child->{cols});
      $crow->set_from_related($_, $row) for @{ $after->{revs} };
      $self->_upsert_run($child, $crow);
    }
  }

  return $row;
}


sub update_or_new {
    my $self  = shift;
    my $attrs = ( @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {} );
    my $cond  = ref $_[0] eq 'HASH' ? shift : {@_};

    my @lookup_cols;
    my $row = $self->_find_for_write( $cond, $attrs, 'update_or_new', \@lookup_cols );
    if ( defined $row ) {
        $row->update($self->_update_data_for_found_row($cond, \@lookup_cols));
        return $row;
    }

    return $self->new_result($cond);
}


sub update_or_new_async {
  my $self  = shift;
  my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
  my $cond  = ref $_[0] eq 'HASH' ? shift : {@_};

  return $self->_rs_run_async('update_or_new',
    sub {
      my ($backend, $fc) = @_;

      my @lookup_cols;
      return $self->_find_for_write_async($cond, $attrs, 'update_or_new', \@lookup_cols)->then(sub {
        my ($row) = @_;
        return $row->update_async($self->_update_data_for_found_row($cond, \@lookup_cols)) if defined $row;
        return $self->new_result($cond);
      });
    },
    sub { $self->update_or_new($cond, $attrs) },
  );
}


sub get_cache {
  shift->{all_cache};
}


sub set_cache {
  my ( $self, $data ) = @_;
  $self->throw_exception("set_cache requires an arrayref")
      if defined($data) && (ref $data ne 'ARRAY');

  # If set_cache is called after a related resultset has been created,
  # we expect subsequent calls to related_resultset to include cached
  # results (if they are present on the objects in $data).
  #
  # This won't be true if we've cached the related_resultsets already,
  # so clear them.
  $self->{related_resultsets} = {};
  $self->{all_cache} = $data;
}


sub clear_cache {
  shift->set_cache(undef);
}


sub is_paged {
  my ($self) = @_;
  return !!$self->{attrs}{page};
}


sub is_ordered {
  my ($self) = @_;
  return scalar $self->result_source->storage->_extract_order_criteria($self->{attrs}{order_by});
}


sub related_resultset {
  my ($self, $rel) = @_;



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