File Coverage

blib/lib/DBIx/Class/Helper/ResultSet/Random.pm
Criterion Covered Total %
statement 34 35 97.1
branch 2 4 50.0
condition 4 5 80.0
subroutine 9 9 100.0
pod 1 1 100.0
total 50 54 92.5


line stmt bran cond sub pod time code
1             package DBIx::Class::Helper::ResultSet::Random;
2             $DBIx::Class::Helper::ResultSet::Random::VERSION = '2.035000';
3             # ABSTRACT: Get random rows from a ResultSet
4              
5 57     57   29678 use strict;
  57         132  
  57         1688  
6 57     57   298 use warnings;
  57         124  
  57         1883  
7              
8 57     57   293 use parent 'DBIx::Class::ResultSet';
  57         115  
  57         323  
9              
10 57     57   3750 use Module::Runtime 'use_module';
  57         144  
  57         462  
11 57     57   2965 use Try::Tiny;
  57         163  
  57         20488  
12              
13             sub _introspector {
14 1     1   11 my $d = use_module('DBIx::Introspector')
15             ->new(drivers => '2013-12.01');
16              
17 1         6150 $d->decorate_driver_unconnected(ACCESS => rand_sql => 'RND()' );
18 1         110 $d->decorate_driver_unconnected(Oracle => rand_sql => 'dbms_random.value' );
19 1         35 $d->decorate_driver_unconnected(Pg => rand_sql => 'RANDOM()' );
20 1         33 $d->decorate_driver_unconnected(MSSQL => rand_sql => 'NEWID()' );
21 1         32 $d->decorate_driver_unconnected(SQLite => rand_sql => 'RANDOM()' );
22              
23 1         30 $d
24             }
25              
26             my $d;
27             sub _rand_order_by {
28 2     2   5 my $self = shift;
29 2         10 my $storage = $self->result_source->storage;
30 2         102 $storage->ensure_connected;
31              
32 2   66     532 $d ||= $self->_introspector;
33 2     2   16 return try { $d->get($storage->dbh, undef, 'rand_sql') } catch { 'RAND()' };
  2         102  
  0         0  
34             }
35              
36             sub rand {
37 2     2 1 121 my $self = shift;
38 2   100     14 my $amount = shift || 1;
39              
40 2 50       8 $self->throw_exception('rand can only return a positive amount of rows')
41             unless $amount > 0;
42              
43 2 50       24 $self->throw_exception('rand can only return an integer amount of rows')
44             unless $amount == int $amount;
45              
46 2         11 my $order_by = $self->_rand_order_by;
47              
48 2         789 return $self->search(undef, { rows=> $amount, order_by => \$order_by});
49             }
50              
51             1;
52              
53             __END__
54              
55             =pod
56              
57             =head1 NAME
58              
59             DBIx::Class::Helper::ResultSet::Random - Get random rows from a ResultSet
60              
61             =head1 SYNOPSIS
62              
63             # note that this is normally a component for a ResultSet
64             package MySchema::ResultSet::Bar;
65              
66             use strict;
67             use warnings;
68              
69             use parent 'DBIx::Class::ResultSet';
70              
71             __PACKAGE__->load_components('Helper::ResultSet::Random');
72              
73             # in code using resultset:
74             my $random_row = $schema->resultset('Bar')->rand->single;
75              
76             =head1 DESCRIPTION
77              
78             This component allows convenient selection of random rows. See
79             L<DBIx::Class::Helper::ResultSet/NOTE> for a nice way to apply it to your
80             entire schema.
81              
82             Currently this works by doing something akin to
83              
84             SELECT TOP($x) from $table ORDER BY RANDOM()
85              
86             Lots of people think this is slow. My own benchmarks show that doing the above,
87             for 10 rows in a table with just over 8 million rows, is nearly instant.
88             Although that was with SQL Server, and different databases will handle that
89             differently.
90              
91             So please, if you have performance issues and want this to work with your
92             database, get in touch and I will do what I can to get it to work quickly enough
93             to suite your needs.
94              
95             =head1 METHODS
96              
97             =head2 rand
98              
99             This method takes a single argument, being the size of the random ResultSet
100             to return. It defaults to 1. This Component will throw exceptions if the
101             argument is not an integer or not greater than zero.
102              
103             =head2 _rand_order_by
104              
105             This module currently does an C<ORDER BY> on some db specific function. If for
106             some reason it guesses incorrectly for your database the easiest way to fix
107             that in the short-term (ie without patching upstream) is to override this
108             method. So for example, if your db uses C<RAND()> instead of C<RANDOM()> and
109             it's not in the predefined list of dbs you could just do the following in your
110             ResultSet class:
111              
112             sub _rand_order_by { 'RAND()' }
113              
114             =head1 AUTHOR
115              
116             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
117              
118             =head1 COPYRIGHT AND LICENSE
119              
120             This software is copyright (c) 2020 by Arthur Axel "fREW" Schmidt.
121              
122             This is free software; you can redistribute it and/or modify it under
123             the same terms as the Perl 5 programming language system itself.
124              
125             =cut