Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Data/ObjectDriver/Driver/DBI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ sub fetch {
my @bind;
my $map = $stmt->select_map;
for my $col (@{ $stmt->select }) {
push @bind, \$rec->{ $map->{$col} || $col };
push @bind, \$rec->{ $map->{$col} };
}

my $dbh = $driver->r_handle($class->properties->{db});
Expand Down
49 changes: 26 additions & 23 deletions lib/Data/ObjectDriver/SQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use Scalar::Util 'blessed';
use base qw( Class::Accessor::Fast );

__PACKAGE__->mk_accessors(qw(
select distinct select_map select_map_reverse
select distinct select_map_reverse
from joins where bind limit offset group order
having where_values column_mutator index_hint
comment as aggrigated
Expand All @@ -19,7 +19,6 @@ sub new {
my $stmt = $class->SUPER::new(@_);
$stmt->select([]);
$stmt->distinct(0);
$stmt->select_map({});
$stmt->select_map_reverse({});
$stmt->bind([]);
$stmt->from([]);
Expand All @@ -31,17 +30,20 @@ sub new {
$stmt;
}

sub select_map {
return { $_[0]->{select_map_reverse} ? reverse %{ $_[0]->{select_map_reverse} } : () };
}

sub add_select {
my $stmt = shift;
my($term, $col) = @_;
$col ||= $term;
push @{ $stmt->select }, $term;
if (blessed($term) && $col->isa('Data::ObjectDriver::SQL')) {
die 'Sub-query requires an alias by setting $stmt->as(...)' unless $term->as;
$stmt->select_map->{$term} = $term->as;
if (blessed($term) && $term->isa('Data::ObjectDriver::SQL')) {
my $alias = $col || $term->as;
die 'Sub-query requires an alias by setting $stmt->as(...)' unless $alias;
$stmt->select_map_reverse->{$alias} = $term;
} else {
$stmt->select_map->{$term} = $col;
$stmt->select_map_reverse->{$col} = $term;
$stmt->select_map_reverse->{$col || $term} = $term;
}
}

Expand Down Expand Up @@ -71,13 +73,15 @@ sub as_sql {
if (@{ $stmt->select }) {
$sql .= 'SELECT ';
$sql .= 'DISTINCT ' if $stmt->distinct;
my $select_map = $stmt->select_map;
$sql .= join(', ', map {
my $col = $_;
my $alias = $select_map->{$col};
if (blessed($col) && $col->isa('Data::ObjectDriver::SQL')) {
push @bind_for_select, @{ $col->{bind} };
$col->as_subquery;
$col->as_subquery($alias);
} else {
if (my $alias = $stmt->select_map->{$col}) {
if ($alias) {
/(?:^|\.)\Q$alias\E$/ ? $col : "$col $alias";
} else {
$col;
Expand Down Expand Up @@ -144,10 +148,11 @@ sub as_sql {
}

sub as_subquery {
my $stmt = shift;
my $subquery = '('. $stmt->as_sql. ')';
if ($stmt->as) {
$subquery .= ' AS ' . $stmt->as;
my ($stmt, $alias) = @_;
my $subquery = '(' . $stmt->as_sql . ')';
$alias ||= $stmt->as;
if ($alias) {
$subquery .= ' AS ' . $alias;
}
$subquery;
}
Expand Down Expand Up @@ -273,7 +278,11 @@ sub add_having {
# Carp::croak("Invalid/unsafe column name $col") unless $col =~ /^[\w\.]+$/;

if (my $orig = $stmt->select_map_reverse->{$col}) {
$col = $orig;
if (blessed($orig) && $orig->isa('Data::ObjectDriver::SQL')) {
# do nothins
} else {
$col = $orig;
}
}

my($term, $bind) = $stmt->_mk_term($col, $val);
Expand Down Expand Up @@ -429,15 +438,9 @@ The database columns to select in a C<SELECT> query.

Whether the C<SELECT> query should return DISTINCT rows only.

=head2 C<select_map> (hashref)

The map of database column names to object fields in a C<SELECT> query. Use
this mapping to convert members of the C<select> list to column names.

=head2 C<select_map_reverse> (hashref)

The map of object fields to database column names in a C<SELECT> query. Use
this map to reverse the C<select_map> mapping where needed.
The map of object fields to database column names in a C<SELECT> query.

=head2 C<from> (arrayref)

Expand Down Expand Up @@ -568,7 +571,7 @@ Creates a new, empty SQL statement.

Adds the database column C<$column> to the list of fields to return in a
C<SELECT> query. The requested object member will be indicated to be C<$term>
in the statement's C<select_map> and C<select_map_reverse> attributes.
in the statement's C<select_map_reverse> attributes.

C<$term> is optional, and defaults to the same value as C<$column>.

Expand Down
61 changes: 60 additions & 1 deletion t/11-sql-with-models.t
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,65 @@ EOF
eval { $stmt->add_select($subquery) };
like $@, qr/requires an alias/;
};

subtest 'set alias by add_select argument' => sub {
my $stmt = Blog->driver->prepare_statement('Blog', [{ name => $blog1->name }], {});
my $subquery = Entry->driver->prepare_statement(
'Entry',
ordered_hashref(blog_id => \'= blog.id', text => 'second'),
{ fetchonly => ['id'], limit => 1 });
$stmt->add_select($subquery, 'sub_alias');

my $expected = sql_normalize(<<'EOF');
SELECT
blog.id,
blog.parent_id,
blog.name,
(
SELECT entry.id
FROM entry
WHERE (entry.blog_id = blog.id) AND (entry.text = ?)
LIMIT 1
) AS sub_alias
FROM blog
WHERE ((name = ?))
EOF

is sql_normalize($stmt->as_sql), sql_normalize($expected), 'right sql';
is_deeply($stmt->{bind}, ['second', $blog1->name], 'right bind values');
my @res = search_by_prepared_statement('Blog', $stmt);
is scalar(@res), 1;
is scalar(keys %{ $res[0]{column_values} }), 4;
is($res[0]{column_values}{id}, $blog1->id);
is($res[0]{column_values}{sub_alias}, $entry12->id);
};
};

subtest 'select_map used in add_having' => sub {
my $stmt = Entry->driver->prepare_statement('Entry', {}, {});
$stmt->add_select('count(*)', 'count');
$stmt->group({column => 'blog_id'});
$stmt->add_having(count => 2);
is sql_normalize($stmt->as_sql), sql_normalize(<<'EOF');
SELECT entry.id, entry.blog_id, entry.title, entry.text, count(*) count
FROM entry
GROUP BY blog_id
HAVING (count(*) = ?)
EOF
is_deeply($stmt->{bind}, ['2'], 'right bind values');

my $subquery = Blog->driver->prepare_statement('Blog', {}, {});
$stmt->add_select($subquery, 'sub');
$stmt->add_having(sub => 3);
is sql_normalize($stmt->as_sql), sql_normalize(<<'EOF');
SELECT
entry.id, entry.blog_id, entry.title, entry.text, count(*) count,
(SELECT blog.id, blog.parent_id, blog.name FROM blog) AS sub
FROM entry
GROUP BY blog_id
HAVING (count(*) = ?) AND (sub = ?)
EOF
is_deeply($stmt->{bind}, ['2', '3'], 'right bind values');
};

subtest 'subquery in from clause' => sub {
Expand Down Expand Up @@ -331,7 +390,7 @@ sub search_by_prepared_statement {
my @bind;
my $map = $stmt->select_map;
for my $col (@{ $stmt->select }) {
push @bind, \$rec->{ $map->{$col} || $col };
push @bind, \$rec->{ $map->{$col} };
}

my $dbh = $driver->r_handle($class->properties->{db});
Expand Down
Loading