DBIx-Class-Async

 view release on metacpan or  search on metacpan

lib/DBIx/Class/Async/Row.pm  view on Meta::CPAN

    } elsif (defined $old && !defined $value) {
        $changed = 1;
    } elsif (defined $old && defined $value) {
        if ($old_ref ne 'SCALAR' || $new_ref ne 'SCALAR') {
            $changed = 1;
        } else {
            # Safe to use string comparison
            if ($old ne $value) {
                $changed = 1;
            }
        }
    }

    if ($changed) {
        $self->{_data}{$col} = $value;
        $self->{_dirty}{$col} = 1;

        # Clear the inflated cache so the next 'get_column' re-inflates the new data
        delete $self->{_inflated}{$col};

        # We delete the top-level key so it doesn't "shadow" the new data
        delete $self->{$col};
    }

    return $value;
}

sub set_columns {
    my ($self, $values) = @_;

    croak("hashref of column-value pairs required")
        unless defined $values && ref $values eq 'HASH';

    while (my ($column, $value) = each %$values) {
        $self->set_column($column, $value);
    }

    return $self;
}

sub _update_internal_state {
    my ($self, $res) = @_;

    # 1. Update the internal data hash directly
    # This assumes $res contains the updated column data
    if (ref $res eq 'HASH') {
        foreach my $col (keys %$res) {
            $self->{_data}{$col} = $res->{$col};
            # Optionally update the shadow variable if necessary
            $self->{$col} = $res->{$col} if exists $self->{$col};
        }
    }

    # 2. Update state flags
    $self->{_in_storage} = 1;
    delete $self->{_dirty}; # Clear dirty flags

    return $self;
}

sub update {
    my ($self, $values) = @_;

    if (!$self->{_schema_instance}) {
        # Return a failed future to prevent further cascading failures
        return Future->fail("ResultSet is not initialized with a schema instance.");
    }

    unless ($self->in_storage) {
        return Future->fail("Cannot update row: not in storage. Did you mean to call insert or update_or_insert?");
    }

    if ($values) {
        croak("Usage: update({ col => val })") unless ref $values eq 'HASH';
        foreach my $col (keys %$values) {
            $self->set_column($col, $values->{$col});
        }
    }

    return $self->update_or_insert->on_done(sub {
        # 1. Get the source and necessary metadata
        my $source = $self->result_source;
        my $schema = $source->schema;
        my $source_name = $source->source_name;

        # 2. Explicitly create the custom ResultSet object using your new() method
        my $rs = DBIx::Class::Async::ResultSet->new(
            schema_instance => $schema,
            source_name     => $source_name,
            async_db        => $self->{_async_db},
        );

        # 3. Construct the PK condition
        my @pk_cols = $source->primary_columns;
        my %pk_cond = map { $_ => $self->get_column($_) } @pk_cols;

        # 4. Now we can safely call _generate_cache_key
        my $cache_key = $rs->_generate_cache_key(0, \%pk_cond);

        $rs->clear_cache($cache_key);
        return $self->_update_internal_state($rs);
    });
}

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

    my $async_db    = $self->{_async_db};
    my $source_name = $self->{_source_name};
    my $source      = $self->result_source;
    my ($pk_col)    = $source->primary_columns;

    # 1. Apply changes to the object
    if ($data && ref $data eq 'HASH') {
        foreach my $col (keys %$data) {
            $self->set_column($col, $data->{$col});
        }
    }

    my $is_update = $self->in_storage;

    # 2. Prepare Payload
    my %raw_payload = $is_update ? $self->get_dirty_columns : %{ $self->{_data} // {} };
    my %to_save;

    foreach my $col (keys %raw_payload) {
        # If it's not in _inflated, fall back to the raw value in %raw_payload.
        my $val  = exists $self->{_inflated}{$col} ? $self->{_inflated}{$col} : $raw_payload{$col};
        my $info = $source->column_info($col);

        # If a deflate handler exists and we have a reference, turn it into a string
        if ($info && $info->{deflate} && defined $val && ref $val) {
            $val = $info->{deflate}->($val, $self);
        }

        $to_save{$col} = $val;
    }

    # 3. Success handler
    my $on_success = sub {
        my ($res) = @_;

        # We check if it's an object FIRST before asking what kind of object it is.
        if (blessed($res) && $res->isa('DBIx::Class::Exception')) {
             return Future->fail($res->msg, 'db_error');
        }

        # Also check for the HASH-style error envelope which we saw in your logs
        if (ref $res eq 'HASH' && ($res->{error} || $res->{__error})) {
             my $err = $res->{error} // $res->{__error};
            return Future->fail($err, 'db_error');
        }

        # Normalise data source
        my $final_data;
        if (ref $res && ref $res eq 'HASH') {
            $final_data = $res;
        }
        elsif (ref $res && eval { $res->can('get_columns') }) {
            # Handle case where $res is another Row object
            my %cols = $res->get_columns;
            $final_data = \%cols;
        }
        else {
            # Scalar result (ID) or fallback



( run in 0.502 second using v1.01-cache-2.11-cpan-39bf76dae61 )