Fey

 view release on metacpan or  search on metacpan

lib/Fey/SQL/Update.pm  view on Meta::CPAN

package Fey::SQL::Update;

use strict;
use warnings;
use namespace::autoclean;

our $VERSION = '0.44';

use Fey::Exceptions qw( param_error );
use Fey::Literal;
use Fey::Types qw( ArrayRef CanQuote ColumnWithTable NonNullableUpdateValue
    NullableUpdateValue Table );
use overload ();
use Scalar::Util qw( blessed );

use Moose 2.1200;
use MooseX::Params::Validate 0.21 qw( pos_validated_list );
use MooseX::SemiAffordanceAccessor 0.03;
use MooseX::StrictConstructor 0.13;

with 'Fey::Role::SQL::HasOrderByClause', 'Fey::Role::SQL::HasLimitClause';

with 'Fey::Role::SQL::HasWhereClause' => {
    -excludes => 'bind_params',
    -alias    => { bind_params => '_where_clause_bind_params' },
};

with 'Fey::Role::SQL::HasBindParams' => {
    -excludes => 'bind_params',
    -alias    => { bind_params => '_update_bind_params' },
};

has '_update' => (
    is       => 'rw',
    isa      => ArrayRef,
    default  => sub { [] },
    init_arg => undef,
);

has '_set_pairs' => (
    traits  => ['Array'],
    is      => 'bare',
    isa     => ArrayRef [ArrayRef],
    default => sub { [] },
    handles => {
        _add_set_pair => 'push',
        _set_pairs    => 'elements',
    },
    init_arg => undef,
);

with 'Fey::Role::SQL::Cloneable';

sub update {
    my $self = shift;

    my $count = @_ ? @_ : 1;
    my (@tables) = pos_validated_list(
        \@_,
        ( ( { isa => Table } ) x $count ),
        MX_PARAMS_VALIDATE_NO_CACHE => 1,
    );

    $self->_set_update( \@tables );

    return $self;
}

sub set {
    my $self = shift;

    if ( !@_ || @_ % 2 ) {
        my $count = @_;
        param_error
            "The set method expects a list of paired column objects and values but you passed $count parameters";
    }

    my @spec;
    for ( my $x = 0; $x < @_; $x += 2 ) {
        push @spec, { isa => ColumnWithTable };
        push @spec,
            blessed $_[$x] && $_[$x]->is_nullable()
            ? { isa => NullableUpdateValue }
            : { isa => NonNullableUpdateValue };
    }

    my @set
        = pos_validated_list( \@_, @spec, MX_PARAMS_VALIDATE_NO_CACHE => 1 );

    for ( my $x = 0; $x < @_; $x += 2 ) {
        my $val = $_[ $x + 1 ];

        $val .= ''
            if blessed $val && overload::Overloaded($val);

        if ( !blessed $val ) {
            if ( defined $val && $self->auto_placeholders() ) {
                $self->_add_bind_param($val);

                $val = Fey::Placeholder->new();
            }
            else {
                $val = Fey::Literal->new_from_scalar($val);
            }
        }

        $self->_add_set_pair( [ $_[$x], $val ] );
    }

    return $self;
}

sub sql {
    my $self = shift;
    my ($dbh) = pos_validated_list( \@_, { isa => CanQuote } );

    return (
        join ' ',
        $self->update_clause($dbh),
        $self->set_clause($dbh),
        $self->where_clause($dbh),
        $self->order_by_clause($dbh),
        $self->limit_clause($dbh),
    );
}

sub update_clause {
    return 'UPDATE ' . $_[0]->_tables_subclause( $_[1] );
}

sub _tables_subclause {
    return (
        join ', ',
        map { $_[1]->quote_identifier( $_->name() ) } @{ $_[0]->_update() }
    );
}

sub set_clause {
    my $self = shift;
    my $dbh  = shift;

    # SQLite objects when the table name is provided ("User"."email")
    # on the LHS of the set. I'm hoping that a DBMS which allows a
    # multi-table update also allows the table name in the LHS.
    my $col_quote = @{ $self->_update() } > 1 ? '_name_and_table' : '_name';

    return (
        'SET ' . (
            join ', ',
            map {
                my $val     = $_->[1];
                my $val_sql = $val->sql($dbh);
                $val_sql = "($val_sql)"
                    if blessed $val
                    && $val->can('does')
                    && $val->does('Fey::Role::SQL::ReturnsData');
                $self->$col_quote( $_->[0], $dbh ) . ' = ' . $val_sql;
            } $self->_set_pairs()
        )
    );
}

sub _name_and_table {
    return $_[1]->sql( $_[2] );
}

sub _name {
    return $_[2]->quote_identifier( $_[1]->name() );
}

sub bind_params {
    my $self = shift;

    return (
        $self->_update_bind_params(),
        $self->_where_clause_bind_params(),
    );
}

__PACKAGE__->meta()->make_immutable();

1;

# ABSTRACT: Represents a UPDATE query

__END__



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