api/v0: add a new model implementation
Use DBI exclusively to have a fine control of the produced queries. dbic requires a lot of symbols permutation to produce the desired ones.
This commit is contained in:
parent
b052526f4e
commit
0635e37e13
4 changed files with 318 additions and 0 deletions
84
lib/Pooru/API/V0/Model/Media.pm
Normal file
84
lib/Pooru/API/V0/Model/Media.pm
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package Pooru::API::V0::Model::Media;
|
||||||
|
use v5.40;
|
||||||
|
|
||||||
|
use Pooru::API::V0::Model::PagedResults;
|
||||||
|
|
||||||
|
my $slice = {};
|
||||||
|
|
||||||
|
sub where_in ($k, @v) { "$k IN (" . join(",", split(//, "?" x @v)) . ")" }
|
||||||
|
|
||||||
|
|
||||||
|
sub new ($class, $dbh) { bless {_dbh => $dbh}, $class }
|
||||||
|
|
||||||
|
sub count ($self)
|
||||||
|
{
|
||||||
|
return $self->{_dbh}->selectrow_arrayref(q{
|
||||||
|
SELECT COUNT(*) FROM media
|
||||||
|
})->[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub random_id ($self)
|
||||||
|
{
|
||||||
|
my $row = $self->{_dbh}->selectrow_arrayref(q{
|
||||||
|
SELECT id FROM media ORDER BY random() LIMIT 1
|
||||||
|
});
|
||||||
|
|
||||||
|
return defined($row) ? $row->[0] : undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get ($self, @ids)
|
||||||
|
{
|
||||||
|
my $where_clause = "";
|
||||||
|
$where_clause = "WHERE " . where_in("id", @ids) if @ids > 0;
|
||||||
|
|
||||||
|
my $sth = $self->{_dbh}->prepare(qq{
|
||||||
|
SELECT * FROM media $where_clause ORDER BY id DESC
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
|
||||||
|
$slice, @ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub with_all_tags ($self, @tag_ids)
|
||||||
|
{
|
||||||
|
my $where_clause = where_in("tag_id", @tag_ids);
|
||||||
|
|
||||||
|
# "0 + ?" forces a numeric cast. Otherwise, it's interpreted as TEXT.
|
||||||
|
my $stmt = qq{
|
||||||
|
SELECT
|
||||||
|
media_id
|
||||||
|
FROM media_tag
|
||||||
|
WHERE $where_clause
|
||||||
|
GROUP BY media_id
|
||||||
|
HAVING COUNT(media_id) = 0 + ?
|
||||||
|
ORDER BY media_id DESC
|
||||||
|
};
|
||||||
|
my $sth = $self->{_dbh}->prepare($stmt);
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
|
||||||
|
$slice, (@tag_ids, scalar @tag_ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub similar ($self, $id)
|
||||||
|
{
|
||||||
|
my $sth = $self->{_dbh}->prepare(qq{
|
||||||
|
SELECT
|
||||||
|
media_id,
|
||||||
|
COUNT(tag_id) AS score
|
||||||
|
FROM media_tag
|
||||||
|
WHERE tag_id IN (
|
||||||
|
SELECT
|
||||||
|
tag_id
|
||||||
|
FROM media_tag
|
||||||
|
WHERE media_id = ?
|
||||||
|
) AND media_id != ?
|
||||||
|
GROUP BY media_id
|
||||||
|
ORDER BY score DESC, media_id DESC
|
||||||
|
LIMIT ?
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::Results->new($sth, $slice,
|
||||||
|
($id, $id, $self->SIMILAR_ROWS));
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
59
lib/Pooru/API/V0/Model/PagedResults.pm
Normal file
59
lib/Pooru/API/V0/Model/PagedResults.pm
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package Pooru::API::V0::Model::PagedResults;
|
||||||
|
use v5.40;
|
||||||
|
use parent "Pooru::API::V0::Model::Results";
|
||||||
|
|
||||||
|
|
||||||
|
sub new ($class, $sth, $pgsz, $slice, @bind_values)
|
||||||
|
{
|
||||||
|
my $self = $class->SUPER::new($sth, $slice, (@bind_values, $pgsz, 0));
|
||||||
|
|
||||||
|
$self->{_current} = 1;
|
||||||
|
$self->{_pgsz} = $pgsz;
|
||||||
|
$self->{_offset} = @bind_values + 1;
|
||||||
|
|
||||||
|
my $dbh = $sth->{Database};
|
||||||
|
$self->{_rows} = $dbh->selectrow_arrayref(qq{
|
||||||
|
SELECT COUNT(*) FROM ($sth->{Statement})
|
||||||
|
}, undef, @bind_values)->[0];
|
||||||
|
$self->{_sth} = $dbh->prepare(qq{$sth->{Statement} LIMIT ? OFFSET ?});
|
||||||
|
|
||||||
|
{
|
||||||
|
use integer;
|
||||||
|
$self->{_npages} = $self->{_rows} / $self->{_pgsz} + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub page_size ($self) { $self->{_pgsz} }
|
||||||
|
sub rows ($self) { $self->{_rows} }
|
||||||
|
|
||||||
|
sub page ($self, $n)
|
||||||
|
{
|
||||||
|
$n = $n < 1 ? 1 : $n > $self->{_npages} ? $self->{_npages} : $n;
|
||||||
|
$self->{_current} = $n;
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub pager ($self)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
first_page => 1,
|
||||||
|
previous_page => $self->{_current} > 1 ?
|
||||||
|
$self->{_current} - 1 : undef,
|
||||||
|
current_page => $self->{_current},
|
||||||
|
next_page => $self->{_current} < $self->{_npages} ?
|
||||||
|
$self->{_current} + 1 : undef,
|
||||||
|
last_page => $self->{_npages},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fetchall ($self, $slice = $self->{_slice}) {
|
||||||
|
$self->{_bind_values}->[$self->{_offset}] =
|
||||||
|
$self->{_pgsz} * ($self->{_current} - 1);
|
||||||
|
|
||||||
|
return $self->SUPER::fetchall($slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
30
lib/Pooru/API/V0/Model/Results.pm
Normal file
30
lib/Pooru/API/V0/Model/Results.pm
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package Pooru::API::V0::Model::Results;
|
||||||
|
use v5.40;
|
||||||
|
|
||||||
|
|
||||||
|
sub new ($class, $sth, $slice, @bind_values)
|
||||||
|
{
|
||||||
|
my $self = {
|
||||||
|
_sth => $sth,
|
||||||
|
_slice => $slice,
|
||||||
|
_bind_values => [@bind_values],
|
||||||
|
};
|
||||||
|
|
||||||
|
return bless $self, $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fetchall ($self, $slice = $self->{_slice})
|
||||||
|
{
|
||||||
|
$self->{_sth}->execute($self->{_bind_values}->@*);
|
||||||
|
|
||||||
|
return $self->{_sth}->fetchall_arrayref($slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub single ($self, $slice = $self->{_slice})
|
||||||
|
{
|
||||||
|
my $rows = $self->fetchall($slice);
|
||||||
|
|
||||||
|
return $rows->@* == 1 ? $rows->[0] : undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
145
lib/Pooru/API/V0/Model/Tags.pm
Normal file
145
lib/Pooru/API/V0/Model/Tags.pm
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package Pooru::API::V0::Model::Tags;
|
||||||
|
use v5.40;
|
||||||
|
|
||||||
|
use Pooru::API::V0::Model::PagedResults;
|
||||||
|
|
||||||
|
my $slice = {};
|
||||||
|
|
||||||
|
sub where_in ($k, @v) { "$k IN (" . join(",", split(//, "?" x @v)) . ")" }
|
||||||
|
|
||||||
|
|
||||||
|
sub new ($class, $dbh) { bless {_dbh => $dbh}, $class }
|
||||||
|
|
||||||
|
sub count ($self)
|
||||||
|
{
|
||||||
|
return $self->{_dbh}->selectrow_arrayref(q{
|
||||||
|
SELECT COUNT(*) FROM tag
|
||||||
|
})->[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub random_id ($self)
|
||||||
|
{
|
||||||
|
my $row = $self->{_dbh}->selectrow_arrayref(q{
|
||||||
|
SELECT id FROM tag ORDER BY random() LIMIT 1
|
||||||
|
});
|
||||||
|
|
||||||
|
return defined($row) ? $row->[0] : undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get ($self, %search)
|
||||||
|
{
|
||||||
|
my $key = exists($search{id}) ?
|
||||||
|
"id" : exists($search{display}) ?
|
||||||
|
"display" : "name";
|
||||||
|
my $where_clause = where_in($key, $search{$key}->@*);
|
||||||
|
my @bind_values = $search{$key}->@*;
|
||||||
|
|
||||||
|
if (exists($search{kind_id})) {
|
||||||
|
$where_clause .= " AND kind_id = ?";
|
||||||
|
push(@bind_values, $search{kind_id});
|
||||||
|
}
|
||||||
|
|
||||||
|
my $sth = $self->{_dbh}->prepare(qq{
|
||||||
|
SELECT *
|
||||||
|
FROM tag
|
||||||
|
WHERE $where_clause
|
||||||
|
LIMIT ?
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::Results->new($sth, $slice,
|
||||||
|
(@bind_values, $self->ROWS));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _ranked ($self, $stmt, @bind_values)
|
||||||
|
{
|
||||||
|
my $sth = $self->{_dbh}->prepare($stmt);
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::PagedResults->new($sth, $self->ROWS,
|
||||||
|
$slice, @bind_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ranked ($self) {
|
||||||
|
my $stmt = q{
|
||||||
|
SELECT
|
||||||
|
tag.id,
|
||||||
|
tag.name,
|
||||||
|
tag.kind_id,
|
||||||
|
tag.display,
|
||||||
|
COUNT(*) AS count
|
||||||
|
FROM media_tag, tag
|
||||||
|
WHERE media_tag.tag_id = tag.id
|
||||||
|
GROUP BY tag.id
|
||||||
|
ORDER BY count DESC, kind_id DESC, id ASC
|
||||||
|
};
|
||||||
|
|
||||||
|
return $self->_ranked($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ranked_for_tags ($self, @ids)
|
||||||
|
{
|
||||||
|
my $where_clause = where_in("tag.id", @ids);
|
||||||
|
my $stmt = qq{
|
||||||
|
SELECT
|
||||||
|
tag.id,
|
||||||
|
tag.name,
|
||||||
|
tag.kind_id,
|
||||||
|
tag.display,
|
||||||
|
COUNT(*) AS count
|
||||||
|
FROM media_tag, tag
|
||||||
|
WHERE media_tag.tag_id = tag.id AND $where_clause
|
||||||
|
GROUP BY tag.id
|
||||||
|
ORDER BY count DESC, kind_id DESC, id ASC
|
||||||
|
};
|
||||||
|
|
||||||
|
return $self->_ranked($stmt, @ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ranked_for_media ($self, @ids)
|
||||||
|
{
|
||||||
|
my $where_clause = where_in("media_tag.media_id", @ids);
|
||||||
|
my $stmt = qq{
|
||||||
|
SELECT
|
||||||
|
tag_count.id,
|
||||||
|
tag_count.name,
|
||||||
|
tag_count.kind_id,
|
||||||
|
tag_count.display,
|
||||||
|
tag_count.count
|
||||||
|
FROM
|
||||||
|
media_tag,
|
||||||
|
(SELECT
|
||||||
|
tag.id,
|
||||||
|
tag.name,
|
||||||
|
tag.kind_id,
|
||||||
|
tag.display,
|
||||||
|
COUNT(*) AS count
|
||||||
|
FROM media_tag, tag
|
||||||
|
WHERE media_tag.tag_id = tag.id
|
||||||
|
GROUP BY tag.id) tag_count
|
||||||
|
WHERE media_tag.tag_id = tag_count.id AND $where_clause
|
||||||
|
ORDER BY count DESC, kind_id DESC, id ASC
|
||||||
|
};
|
||||||
|
|
||||||
|
return $self->_ranked($stmt, @ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub search ($self, $q)
|
||||||
|
{
|
||||||
|
my $sth = $self->{_dbh}->prepare(qq{
|
||||||
|
SELECT
|
||||||
|
tag.id,
|
||||||
|
tag.name,
|
||||||
|
tag.kind_id,
|
||||||
|
tag.display,
|
||||||
|
COUNT(*) AS count
|
||||||
|
FROM media_tag, tag
|
||||||
|
WHERE media_tag.tag_id = tag.id AND tag.name LIKE ? ESCAPE '\\'
|
||||||
|
GROUP BY tag.id
|
||||||
|
ORDER BY count DESC, tag.kind_id DESC, tag.id ASC
|
||||||
|
LIMIT ?
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pooru::API::V0::Model::Results->new($sth, $slice,
|
||||||
|
($q, $self->SEARCH_ROWS));
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
Loading…
Add table
Add a link
Reference in a new issue