File | /project/perl/lib/SQL/Abstract/Limit.pm |
Statements Executed | 21 |
Statement Execution Time | 22.8ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
0 | 0 | 0 | 0s | 0s | BEGIN | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _FetchFirst | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _First | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _GenericSubQ | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _LimitOffset | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _LimitXY | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _LimitYX | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _RowNum | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _RowsTo | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _Top | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _default_limit_syntax | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _emulate_limit | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _find_database_from_cdbi | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _find_database_from_dbh | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _find_syntax | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _find_syntax_from_database | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _get_args | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | _order_directions | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | select | SQL::Abstract::Limit::
0 | 0 | 0 | 0s | 0s | where | SQL::Abstract::Limit::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package SQL::Abstract::Limit; | ||||
2 | 3 | 95µs | 1 | 24µs | use strict; # spent 24µs making 1 call to strict::import |
3 | 3 | 82µs | 1 | 131µs | use warnings; # spent 131µs making 1 call to warnings::import |
4 | 3 | 49µs | use Carp(); | ||
5 | |||||
6 | 3 | 7.58ms | use DBI::Const::GetInfoType (); | ||
7 | |||||
8 | 3 | 10.8ms | 1 | 256µs | use SQL::Abstract 1.20; # spent 256µs making 1 call to UNIVERSAL::VERSION |
9 | |||||
10 | 3 | 4.12ms | 1 | 0s | use base 'SQL::Abstract'; # spent 284µs making 1 call to base::import, recursion: max depth 1, time 284µs |
11 | |||||
12 | =head1 NAME | ||||
13 | |||||
14 | SQL::Abstract::Limit - portable LIMIT emulation | ||||
15 | |||||
16 | =cut | ||||
17 | |||||
18 | 1 | 6µs | our $VERSION = '0.12'; | ||
19 | |||||
20 | # additions / error reports welcome ! | ||||
21 | 1 | 43µs | our %SyntaxMap = ( mssql => 'Top', | ||
22 | access => 'Top', | ||||
23 | sybase => 'GenericSubQ', | ||||
24 | oracle => 'RowNum', | ||||
25 | db2 => 'FetchFirst', | ||||
26 | ingres => '', | ||||
27 | adabasd => '', | ||||
28 | informix => 'First', | ||||
29 | |||||
30 | # asany => '', | ||||
31 | |||||
32 | # more recent MySQL versions support LimitOffset as well | ||||
33 | mysql => 'LimitXY', | ||||
34 | mysqlpp => 'LimitXY', | ||||
35 | maxdb => 'LimitXY', # MySQL | ||||
36 | |||||
37 | pg => 'LimitOffset', | ||||
38 | pgpp => 'LimitOffset', | ||||
39 | |||||
40 | sqlite => 'LimitOffset', | ||||
41 | sqlite2 => 'LimitOffset', | ||||
42 | |||||
43 | interbase => 'RowsTo', | ||||
44 | |||||
45 | unify => '', | ||||
46 | primebase => '', | ||||
47 | mimer => '', | ||||
48 | |||||
49 | # anything that uses SQL::Statement can use LimitXY, I think | ||||
50 | sprite => 'LimitXY', | ||||
51 | wtsprite => 'LimitXY', | ||||
52 | anydata => 'LimitXY', | ||||
53 | csv => 'LimitXY', | ||||
54 | ram => 'LimitXY', | ||||
55 | dbm => 'LimitXY', | ||||
56 | excel => 'LimitXY', | ||||
57 | google => 'LimitXY', | ||||
58 | ); | ||||
59 | |||||
60 | |||||
61 | =head1 SYNOPSIS | ||||
62 | |||||
63 | use SQL::Abstract::Limit; | ||||
64 | |||||
65 | my $sql = SQL::Abstract::Limit->new( limit_dialect => 'LimitOffset' );; | ||||
66 | |||||
67 | # or autodetect from a DBI $dbh: | ||||
68 | my $sql = SQL::Abstract::Limit->new( limit_dialect => $dbh ); | ||||
69 | |||||
70 | # or from a Class::DBI class: | ||||
71 | my $sql = SQL::Abstract::Limit->new( limit_dialect => 'My::CDBI::App' ); | ||||
72 | |||||
73 | # or object: | ||||
74 | my $obj = My::CDBI::App->retrieve( $id ); | ||||
75 | my $sql = SQL::Abstract::Limit->new( limit_dialect => $obj ); | ||||
76 | |||||
77 | # generate SQL: | ||||
78 | my ( $stmt, @bind ) = $sql->select( $table, \@fields, \%where, \@order, $limit, $offset ); | ||||
79 | |||||
80 | # Then, use these in your DBI statements | ||||
81 | my $sth = $dbh->prepare( $stmt ); | ||||
82 | $sth->execute( @bind ); | ||||
83 | |||||
84 | # Just generate the WHERE clause (only available for some syntaxes) | ||||
85 | my ( $stmt, @bind ) = $sql->where( \%where, \@order, $limit, $offset ); | ||||
86 | |||||
87 | =head1 DESCRIPTION | ||||
88 | |||||
89 | Portability layer for LIMIT emulation. | ||||
90 | |||||
91 | =over 4 | ||||
92 | |||||
93 | =item new( case => 'lower', cmp => 'like', logic => 'and', convert => 'upper', limit_dialect => 'Top' ) | ||||
94 | |||||
95 | All settings are optional. | ||||
96 | |||||
97 | =over 8 | ||||
98 | |||||
99 | =item limit_dialect | ||||
100 | |||||
101 | Sets the default syntax model to use for emulating a C<LIMIT $rows OFFSET $offset> | ||||
102 | clause. Default setting is C<GenericSubQ>. You can still pass other syntax | ||||
103 | settings in method calls, this just sets the default. Possible values are: | ||||
104 | |||||
105 | LimitOffset PostgreSQL, SQLite | ||||
106 | LimitXY MySQL, MaxDB, anything that uses SQL::Statement | ||||
107 | LimitYX SQLite (optional) | ||||
108 | RowsTo InterBase/FireBird | ||||
109 | |||||
110 | Top SQL/Server, MS Access | ||||
111 | RowNum Oracle | ||||
112 | FetchFirst DB2 | ||||
113 | First Informix # not implemented yet | ||||
114 | GenericSubQ Sybase, plus any databases not recognised by this module | ||||
115 | |||||
116 | $dbh a DBI database handle | ||||
117 | |||||
118 | CDBI subclass | ||||
119 | CDBI object | ||||
120 | |||||
121 | other DBI-based thing | ||||
122 | |||||
123 | The first group are implemented by appending a short clause to the end of the | ||||
124 | statement. The second group require more intricate wrapping of the original | ||||
125 | statement in subselects. | ||||
126 | |||||
127 | You can pass a L<DBI|DBI> database handle, and the module will figure out which | ||||
128 | dialect to use. | ||||
129 | |||||
130 | You can pass a L<Class::DBI|Class::DBI> subclass or object, and the module will | ||||
131 | find the C<$dbh> and use it to find the dialect. | ||||
132 | |||||
133 | Anything else based on L<DBI|DBI> can be easily added by locating the C<$dbh>. | ||||
134 | Patches or suggestions welcome. | ||||
135 | |||||
136 | =back | ||||
137 | |||||
138 | Other options are described in L<SQL::Abstract|SQL::Abstract>. | ||||
139 | |||||
140 | =item select( $table, \@fields, $where, [ \@order, [ $rows, [ $offset ], [ $dialect ] ] ] ) | ||||
141 | |||||
142 | Same as C<SQL::Abstract::select>, but accepts additional C<$rows>, C<$offset> | ||||
143 | and C<$dialect> parameters. | ||||
144 | |||||
145 | The C<$order> parameter is required if C<$rows> is specified. | ||||
146 | |||||
147 | The C<$fields> parameter is required, but can be set to C<undef>, C<''> or | ||||
148 | C<'*'> (all these get set to C<'*'>). | ||||
149 | |||||
150 | The C<$where> parameter is also required. It can be a hashref | ||||
151 | or an arrayref, or C<undef>. | ||||
152 | |||||
153 | =cut | ||||
154 | |||||
155 | sub select { | ||||
156 | my $self = shift; | ||||
157 | my $table = $self->_table(shift); | ||||
158 | my $fields = shift; | ||||
159 | my $where = shift; # if ref( $_[0] ) eq 'HASH'; | ||||
160 | |||||
161 | my ( $order, $rows, $offset, $syntax ) = $self->_get_args( @_ ); | ||||
162 | |||||
163 | $fields ||= '*'; # in case someone supplies '' or undef | ||||
164 | |||||
165 | # with no LIMIT parameters, defer to SQL::Abstract [ don't know why the first way fails ] | ||||
166 | # return $self->SUPER::select( $table, $fields, $where, $order ) unless $rows; | ||||
167 | return SQL::Abstract->new->select( $table, $fields, $where, $order ) unless $rows; | ||||
168 | |||||
169 | # with LIMIT parameters, get the basic SQL without the ORDER BY clause | ||||
170 | my ( $sql, @bind ) = $self->SUPER::select( $table, $fields, $where ); | ||||
171 | |||||
172 | my $syntax_name = $self->_find_syntax( $syntax ); | ||||
173 | |||||
174 | $sql = $self->_emulate_limit( $syntax_name, $sql, $order, $rows, $offset ); | ||||
175 | |||||
176 | return wantarray ? ( $sql, @bind ) : $sql; | ||||
177 | } | ||||
178 | |||||
179 | =item where( [ $where, [ \@order, [ $rows, [ $offset ], [ $dialect ] ] ] ] ) | ||||
180 | |||||
181 | Same as C<SQL::Abstract::where>, but accepts additional C<$rows>, C<$offset> | ||||
182 | and C<$dialect> parameters. | ||||
183 | |||||
184 | Some SQL dialects support syntaxes that can be applied as simple phrases | ||||
185 | tacked on to the end of the WHERE clause. These are: | ||||
186 | |||||
187 | LimitOffset | ||||
188 | LimitXY | ||||
189 | LimitYX | ||||
190 | RowsTo | ||||
191 | |||||
192 | This method returns a modified WHERE clause, if the limit syntax is set to one | ||||
193 | of these options (either in the call to C<where> or in the constructor), and | ||||
194 | if C<$rows> is passed in. | ||||
195 | |||||
196 | Dies via C<croak> if you try to use it for other syntaxes. | ||||
197 | |||||
198 | C<$order> is required if C<$rows> is set. | ||||
199 | |||||
200 | C<$where> is required if any other parameters are specified. It can be a hashref | ||||
201 | or an arrayref, or C<undef>. | ||||
202 | |||||
203 | Returns a regular C<WHERE> clause if no limits are set. | ||||
204 | |||||
205 | =cut | ||||
206 | |||||
207 | sub where | ||||
208 | { | ||||
209 | my $self = shift; | ||||
210 | my $where = shift; # if ref( $_[0] ) eq 'HASH'; | ||||
211 | |||||
212 | my ( $order, $rows, $offset, $syntax ) = $self->_get_args( @_ ); | ||||
213 | |||||
214 | my ( $sql, @bind ); | ||||
215 | |||||
216 | if ( $rows ) | ||||
217 | { | ||||
218 | ( $sql, @bind ) = $self->SUPER::where( $where ); | ||||
219 | |||||
220 | my $syntax_name = $self->_find_syntax( $syntax ); | ||||
221 | |||||
222 | Carp::croak( "can't build a stand-alone WHERE clause for $syntax_name" ) | ||||
223 | unless $syntax_name =~ /(?:LimitOffset|LimitXY|LimitYX|RowsTo)/i; | ||||
224 | |||||
225 | $sql = $self->_emulate_limit( $syntax_name, $sql, $order, $rows, $offset ); | ||||
226 | } | ||||
227 | else | ||||
228 | { | ||||
229 | # | ||||
230 | ( $sql, @bind ) = $self->SUPER::where( $where, $order ); | ||||
231 | } | ||||
232 | |||||
233 | return wantarray ? ( $sql, @bind ) : $sql; | ||||
234 | } | ||||
235 | |||||
236 | sub _get_args { | ||||
237 | my $self = shift; | ||||
238 | |||||
239 | my $order = shift; | ||||
240 | my $rows = shift; | ||||
241 | my $offset = shift if ( $_[0] && $_[0] =~ /^\d+$/ ); | ||||
242 | my $syntax = shift || $self->_default_limit_syntax; | ||||
243 | |||||
244 | return $order, $rows, $offset, $syntax; | ||||
245 | } | ||||
246 | |||||
247 | =item insert | ||||
248 | |||||
249 | =item update | ||||
250 | |||||
251 | =item delete | ||||
252 | |||||
253 | =item values | ||||
254 | |||||
255 | =item generate | ||||
256 | |||||
257 | See L<SQL::Abstract|SQL::Abstract> for these methods. | ||||
258 | |||||
259 | C<update> and C<delete> are not provided with any C<LIMIT> emulation in this | ||||
260 | release, and no support is planned at the moment. But patches would be welcome. | ||||
261 | |||||
262 | =back | ||||
263 | |||||
264 | =cut | ||||
265 | |||||
266 | sub _default_limit_syntax { $_[0]->{limit_dialect} || 'GenericSubQ' } | ||||
267 | |||||
268 | sub _emulate_limit { | ||||
269 | my ( $self, $syntax, $sql, $order, $rows, $offset ) = @_; | ||||
270 | |||||
271 | $offset ||= 0; | ||||
272 | |||||
273 | Carp::croak( "rows must be a number (got $rows)" ) unless $rows =~ /^\d+$/; | ||||
274 | Carp::croak( "offset must be a number (got $offset)" ) unless $offset =~ /^\d+$/; | ||||
275 | |||||
276 | my $method = $self->can( 'emulate_limit' ) || "_$syntax"; | ||||
277 | |||||
278 | $sql = $self->$method( $sql, $order, $rows, $offset ); | ||||
279 | |||||
280 | return $sql; | ||||
281 | } | ||||
282 | |||||
283 | sub _find_syntax | ||||
284 | { | ||||
285 | my ($self, $syntax) = @_; | ||||
286 | |||||
287 | # $syntax is a dialect name, database name, $dbh, or CDBI class or object | ||||
288 | |||||
289 | Carp::croak('no syntax') unless $syntax; | ||||
290 | |||||
291 | my $db; | ||||
292 | |||||
293 | # note: tests arranged so that the eval isn't run against a scalar $syntax | ||||
294 | # see rt #15000 | ||||
295 | if (ref $syntax) # a $dbh or a CDBI object | ||||
296 | { | ||||
297 | if ( UNIVERSAL::isa($syntax => 'Class::DBI') ) | ||||
298 | { | ||||
299 | $db = $self->_find_database_from_cdbi($syntax); | ||||
300 | } | ||||
301 | elsif ( eval { $syntax->{Driver}->{Name} } ) # or use isa DBI::db ? | ||||
302 | { | ||||
303 | $db = $self->_find_database_from_dbh($syntax); | ||||
304 | } | ||||
305 | } | ||||
306 | else # string - CDBI class, db name, or dialect name | ||||
307 | { | ||||
308 | if (exists $SyntaxMap{lc $syntax}) | ||||
309 | { | ||||
310 | # the name of a database | ||||
311 | $db = $syntax; | ||||
312 | } | ||||
313 | elsif (UNIVERSAL::isa($syntax => 'Class::DBI')) | ||||
314 | { | ||||
315 | # a CDBI class | ||||
316 | $db = $self->_find_database_from_cdbi($syntax); | ||||
317 | } | ||||
318 | else | ||||
319 | { | ||||
320 | # or it's already a syntax dialect | ||||
321 | return $syntax; | ||||
322 | } | ||||
323 | } | ||||
324 | |||||
325 | return $self->_find_syntax_from_database($db) if $db; | ||||
326 | |||||
327 | # if you get here, you might like to provide a patch to determine the | ||||
328 | # syntax model for your object or ref e.g. by getting at the $dbh stored in it | ||||
329 | warn "can't determine syntax model for $syntax - using default"; | ||||
330 | |||||
331 | return $self->_default_limit_syntax; | ||||
332 | } | ||||
333 | |||||
334 | # most of this code modified from DBIx::AnyDBD::rebless | ||||
335 | sub _find_database_from_dbh { | ||||
336 | my ( $self, $dbh ) = @_; | ||||
337 | |||||
338 | my $driver = ucfirst( $dbh->{Driver}->{Name} ) || Carp::croak( "no driver in $dbh" ); | ||||
339 | |||||
340 | if ( $driver eq 'Proxy' ) | ||||
341 | { | ||||
342 | # Looking into the internals of DBD::Proxy is maybe a little questionable | ||||
343 | ( $driver ) = $dbh->{proxy_client}->{application} =~ /^DBI:(.+?):/; | ||||
344 | } | ||||
345 | |||||
346 | # what about DBD::JDBC ? | ||||
347 | my ( $odbc, $ado ) = ( $driver eq 'ODBC', $driver eq 'ADO' ); | ||||
348 | |||||
349 | if ( $odbc || $ado ) | ||||
350 | { | ||||
351 | my $name; | ||||
352 | |||||
353 | # $name = $dbh->func( 17, 'GetInfo' ) if $odbc; | ||||
354 | $name = $dbh->get_info( $DBI::Const::GetInfoType::GetInfoType{SQL_DBMS_NAME} ) if $odbc; | ||||
355 | $name = $dbh->{ado_conn}->Properties->Item( 'DBMS Name' )->Value if $ado; | ||||
356 | |||||
357 | die "can't determine driver name for ODBC or ADO handle: $dbh" unless $name; | ||||
358 | |||||
359 | CASE: { | ||||
360 | $driver = 'MSSQL', last CASE if $name eq 'Microsoft SQL Server'; | ||||
361 | $driver = 'Sybase', last CASE if $name eq 'SQL Server'; | ||||
362 | $driver = 'Oracle', last CASE if $name =~ /Oracle/; | ||||
363 | $driver = 'ASAny', last CASE if $name eq 'Adaptive Server Anywhere'; | ||||
364 | $driver = 'AdabasD', last CASE if $name eq 'ADABAS D'; | ||||
365 | |||||
366 | # this should catch Access (ACCESS) and Informix (Informix) | ||||
367 | $driver = lc( $name ); | ||||
368 | $driver =~ s/\b(\w)/uc($1)/eg; | ||||
369 | $driver =~ s/\s+/_/g; | ||||
370 | } | ||||
371 | } | ||||
372 | |||||
373 | die "couldn't find DBD driver in $dbh" unless $driver; | ||||
374 | |||||
375 | # $driver now holds a string identifying the database server - in the future, | ||||
376 | # it might return an object with extra information e.g. version | ||||
377 | return $driver; | ||||
378 | } | ||||
379 | |||||
380 | # $cdbi can be a class or object | ||||
381 | sub _find_database_from_cdbi | ||||
382 | { | ||||
383 | my ($self, $cdbi) = @_; | ||||
384 | |||||
385 | # inherits from Ima::DBI | ||||
386 | my ($dbh) = $cdbi->db_handles; | ||||
387 | |||||
388 | Carp::croak "no \$dbh in $cdbi" unless $dbh; | ||||
389 | |||||
390 | return $self->_find_database_from_dbh($dbh); | ||||
391 | } | ||||
392 | |||||
393 | # currently expects a string (database moniker), but this may become an object | ||||
394 | # with e.g. version string etc. | ||||
395 | sub _find_syntax_from_database { | ||||
396 | my ( $self, $db ) = @_; | ||||
397 | |||||
398 | my $syntax = $SyntaxMap{ lc( $db ) }; | ||||
399 | |||||
400 | return $syntax if $syntax; | ||||
401 | |||||
402 | my $msg = defined $syntax ? | ||||
403 | "no dialect known for $db - using GenericSubQ dialect" : | ||||
404 | "unknown database $db - using GenericSubQ dialect"; | ||||
405 | |||||
406 | warn $msg; | ||||
407 | |||||
408 | return 'GenericSubQ'; | ||||
409 | } | ||||
410 | |||||
411 | # DBIx::SearchBuilder LIMIT emulation: | ||||
412 | # Oracle - RowNum | ||||
413 | # Pg - LimitOffset | ||||
414 | # Sybase - doesn't emulate | ||||
415 | # Informix - First - but can only retrieve 1st page | ||||
416 | # SQLite - default | ||||
417 | # MySQL - default | ||||
418 | |||||
419 | # default - LIMIT $offset, $rows | ||||
420 | # or LIMIT $rows | ||||
421 | # if $offset == 0 | ||||
422 | |||||
423 | # DBIx::Compat also tries, but only for the easy ones | ||||
424 | |||||
425 | |||||
426 | # --------------------------------- | ||||
427 | # LIMIT emulation routines | ||||
428 | |||||
429 | # utility for some emulations | ||||
430 | sub _order_directions { | ||||
431 | my ( $self, $order ) = @_; | ||||
432 | |||||
433 | return unless $order; | ||||
434 | |||||
435 | my $ref = ref $order; | ||||
436 | |||||
437 | my @order; | ||||
438 | |||||
439 | CASE: { | ||||
440 | @order = @$order, last CASE if $ref eq 'ARRAY'; | ||||
441 | @order = ( $order ), last CASE unless $ref; | ||||
442 | @order = ( $$order ), last CASE if $ref eq 'SCALAR'; | ||||
443 | Carp::croak __PACKAGE__ . ": Unsupported data struct $ref for ORDER BY"; | ||||
444 | } | ||||
445 | |||||
446 | my ( $order_by_up, $order_by_down ); | ||||
447 | |||||
448 | foreach my $spec ( @order ) | ||||
449 | { | ||||
450 | my @spec = split ' ', $spec; | ||||
451 | Carp::croak( "bad column order spec: $spec" ) if @spec > 2; | ||||
452 | push( @spec, 'ASC' ) unless @spec == 2; | ||||
453 | my ( $col, $up ) = @spec; # or maybe down | ||||
454 | $up = uc( $up ); | ||||
455 | Carp::croak( "bad direction: $up" ) unless $up =~ /^(?:ASC|DESC)$/; | ||||
456 | $order_by_up .= ", $col $up"; | ||||
457 | my $down = $up eq 'ASC' ? 'DESC' : 'ASC'; | ||||
458 | $order_by_down .= ", $col $down"; | ||||
459 | } | ||||
460 | |||||
461 | s/^,/ORDER BY/ for ( $order_by_up, $order_by_down ); | ||||
462 | |||||
463 | return $order_by_up, $order_by_down; | ||||
464 | } | ||||
465 | |||||
466 | # From http://phplens.com/lens/adodb/tips_portable_sql.htm | ||||
467 | |||||
468 | # When writing SQL to retrieve the first 10 rows for paging, you could write... | ||||
469 | # Database SQL Syntax | ||||
470 | # DB2 select * from table fetch first 10 rows only | ||||
471 | # Informix select first 10 * from table | ||||
472 | # Microsoft SQL Server and Access select top 10 * from table | ||||
473 | # MySQL and PostgreSQL select * from table limit 10 | ||||
474 | # Oracle 8i select * from (select * from table) where rownum <= 10 | ||||
475 | |||||
476 | =head2 Limit emulation | ||||
477 | |||||
478 | The following dialects are available for emulating the LIMIT clause. In each | ||||
479 | case, C<$sql> represents the SQL statement generated by C<SQL::Abstract::select>, | ||||
480 | minus the ORDER BY clause, e.g. | ||||
481 | |||||
482 | SELECT foo, bar FROM my_table WHERE some_conditions | ||||
483 | |||||
484 | C<$sql_after_select> represents C<$sql> with the leading C<SELECT> keyword | ||||
485 | removed. | ||||
486 | |||||
487 | C<order_cols_up> represents the sort column(s) and direction(s) specified in | ||||
488 | the C<order> parameter. | ||||
489 | |||||
490 | C<order_cols_down> represents the opposite sort. | ||||
491 | |||||
492 | C<$last = $rows + $offset> | ||||
493 | |||||
494 | =over 4 | ||||
495 | |||||
496 | =item LimitOffset | ||||
497 | |||||
498 | =over 8 | ||||
499 | |||||
500 | =item Syntax | ||||
501 | |||||
502 | $sql ORDER BY order_cols_up LIMIT $rows OFFSET $offset | ||||
503 | |||||
504 | or | ||||
505 | |||||
506 | $sql ORDER BY order_cols_up LIMIT $rows | ||||
507 | |||||
508 | if C<$offset == 0>. | ||||
509 | |||||
510 | =item Databases | ||||
511 | |||||
512 | PostgreSQL | ||||
513 | SQLite | ||||
514 | |||||
515 | =back | ||||
516 | |||||
517 | =cut | ||||
518 | |||||
519 | sub _LimitOffset { | ||||
520 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
521 | $sql .= $self->_order_by( $order ) . " LIMIT $rows"; | ||||
522 | $sql .= " OFFSET $offset" if +$offset; | ||||
523 | return $sql; | ||||
524 | } | ||||
525 | |||||
526 | =item LimitXY | ||||
527 | |||||
528 | =over 8 | ||||
529 | |||||
530 | =item Syntax | ||||
531 | |||||
532 | $sql ORDER BY order_cols_up LIMIT $offset, $rows | ||||
533 | |||||
534 | or | ||||
535 | |||||
536 | $sql ORDER BY order_cols_up LIMIT $rows | ||||
537 | |||||
538 | if C<$offset == 0>. | ||||
539 | |||||
540 | =item Databases | ||||
541 | |||||
542 | MySQL | ||||
543 | |||||
544 | =back | ||||
545 | |||||
546 | =cut | ||||
547 | |||||
548 | sub _LimitXY { | ||||
549 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
550 | $sql .= $self->_order_by( $order ) . " LIMIT "; | ||||
551 | $sql .= "$offset, " if +$offset; | ||||
552 | $sql .= $rows; | ||||
553 | return $sql; | ||||
554 | } | ||||
555 | |||||
556 | =item LimitYX | ||||
557 | |||||
558 | =over 8 | ||||
559 | |||||
560 | =item Syntax | ||||
561 | |||||
562 | $sql ORDER BY order_cols_up LIMIT $rows, $offset | ||||
563 | |||||
564 | or | ||||
565 | |||||
566 | $sql ORDER BY order_cols_up LIMIT $rows | ||||
567 | |||||
568 | if C<$offset == 0>. | ||||
569 | |||||
570 | =item Databases | ||||
571 | |||||
572 | SQLite understands this syntax, or LimitOffset. If autodetecting the | ||||
573 | dialect, it will be set to LimitOffset. | ||||
574 | |||||
575 | =back | ||||
576 | |||||
577 | =cut | ||||
578 | |||||
579 | sub _LimitYX { | ||||
580 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
581 | $sql .= $self->_order_by( $order ) . " LIMIT $rows"; | ||||
582 | $sql .= " $offset" if +$offset; | ||||
583 | return $sql; | ||||
584 | } | ||||
585 | |||||
586 | =item RowsTo | ||||
587 | |||||
588 | =over 8 | ||||
589 | |||||
590 | =item Syntax | ||||
591 | |||||
592 | $sql ORDER BY order_cols_up ROWS $offset TO $last | ||||
593 | |||||
594 | =item Databases | ||||
595 | |||||
596 | InterBase | ||||
597 | FireBird | ||||
598 | |||||
599 | =back | ||||
600 | |||||
601 | =cut | ||||
602 | |||||
603 | # InterBase/FireBird | ||||
604 | sub _RowsTo { | ||||
605 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
606 | my $last = $rows + $offset; | ||||
607 | $sql .= $self->_order_by( $order ) . " ROWS $offset TO $last"; | ||||
608 | return $sql; | ||||
609 | } | ||||
610 | |||||
611 | =item Top | ||||
612 | |||||
613 | =over 8 | ||||
614 | |||||
615 | =item Syntax | ||||
616 | |||||
617 | SELECT * FROM | ||||
618 | ( | ||||
619 | SELECT TOP $rows * FROM | ||||
620 | ( | ||||
621 | SELECT TOP $last $sql_after_select | ||||
622 | ORDER BY order_cols_up | ||||
623 | ) AS foo | ||||
624 | ORDER BY order_cols_down | ||||
625 | ) AS bar | ||||
626 | ORDER BY order_cols_up | ||||
627 | |||||
628 | |||||
629 | =item Databases | ||||
630 | |||||
631 | SQL/Server | ||||
632 | MS Access | ||||
633 | |||||
634 | =back | ||||
635 | |||||
636 | =cut | ||||
637 | |||||
638 | sub _Top { | ||||
639 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
640 | |||||
641 | my $last = $rows + $offset; | ||||
642 | |||||
643 | my ( $order_by_up, $order_by_down ) = $self->_order_directions( $order ); | ||||
644 | |||||
645 | $sql =~ s/^\s*(SELECT|select)//; | ||||
646 | |||||
647 | $sql = <<""; | ||||
648 | SELECT * FROM | ||||
649 | ( | ||||
650 | SELECT TOP $rows * FROM | ||||
651 | ( | ||||
652 | SELECT TOP $last $sql $order_by_up | ||||
653 | ) AS foo | ||||
654 | $order_by_down | ||||
655 | ) AS bar | ||||
656 | $order_by_up | ||||
657 | |||||
658 | return $sql; | ||||
659 | } | ||||
660 | |||||
661 | |||||
662 | |||||
663 | =item RowNum | ||||
664 | |||||
665 | =over 8 | ||||
666 | |||||
667 | =item Syntax | ||||
668 | |||||
669 | Oracle numbers rows from 1, not zero, so here $offset has been incremented by 1. | ||||
670 | |||||
671 | SELECT * FROM | ||||
672 | ( | ||||
673 | SELECT A.*, ROWNUM r FROM | ||||
674 | ( | ||||
675 | $sql ORDER BY order_cols_up | ||||
676 | ) A | ||||
677 | WHERE ROWNUM <= $last | ||||
678 | ) B | ||||
679 | WHERE r >= $offset | ||||
680 | |||||
681 | =item Databases | ||||
682 | |||||
683 | Oracle | ||||
684 | |||||
685 | =back | ||||
686 | |||||
687 | =cut | ||||
688 | |||||
689 | sub _RowNum { | ||||
690 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
691 | |||||
692 | # Oracle orders from 1 not zero | ||||
693 | $offset++; | ||||
694 | |||||
695 | my $last = $rows + $offset; | ||||
696 | |||||
697 | my $order_by = $self->_order_by( $order ); | ||||
698 | |||||
699 | $sql = <<""; | ||||
700 | SELECT * FROM | ||||
701 | ( | ||||
702 | SELECT A.*, ROWNUM r FROM | ||||
703 | ( | ||||
704 | $sql $order_by | ||||
705 | ) A | ||||
706 | WHERE ROWNUM < $last | ||||
707 | ) B | ||||
708 | WHERE r >= $offset | ||||
709 | |||||
710 | return $sql; | ||||
711 | } | ||||
712 | |||||
713 | # DBIx::SearchBuilder::Handle::Oracle does this: | ||||
714 | |||||
715 | # Transform an SQL query from: | ||||
716 | # | ||||
717 | # SELECT main.* | ||||
718 | # FROM Tickets main | ||||
719 | # WHERE ((main.EffectiveId = main.id)) | ||||
720 | # AND ((main.Type = 'ticket')) | ||||
721 | # AND ( ( (main.Status = 'new')OR(main.Status = 'open') ) | ||||
722 | # AND ( (main.Queue = '1') ) ) | ||||
723 | # | ||||
724 | # to: | ||||
725 | # | ||||
726 | # SELECT * FROM ( | ||||
727 | # SELECT limitquery.*,rownum limitrownum FROM ( | ||||
728 | # SELECT main.* | ||||
729 | # FROM Tickets main | ||||
730 | # WHERE ((main.EffectiveId = main.id)) | ||||
731 | # AND ((main.Type = 'ticket')) | ||||
732 | # AND ( ( (main.Status = 'new')OR(main.Status = 'open') ) | ||||
733 | # AND ( (main.Queue = '1') ) ) | ||||
734 | # ) limitquery WHERE rownum <= 50 | ||||
735 | # ) WHERE limitrownum >= 1 | ||||
736 | # | ||||
737 | #if ($per_page) { | ||||
738 | # # Oracle orders from 1 not zero | ||||
739 | # $first++; | ||||
740 | # # Make current query a sub select | ||||
741 | # $$statementref = "SELECT * FROM ( SELECT limitquery.*,rownum limitrownum FROM ( $$statementref ) limitquery WHERE rownum <= " . ($first + $per_page - 1) . " ) WHERE limitrownum >= " . $first; | ||||
742 | #} | ||||
743 | |||||
744 | # DBIx::SQLEngine::Driver::Oracle does this: | ||||
745 | |||||
746 | #sub sql_limit { | ||||
747 | # my $self = shift; | ||||
748 | # my ( $limit, $offset, $sql, @params ) = @_; | ||||
749 | # | ||||
750 | # # remove tablealiases and group-functions from outer query properties | ||||
751 | # my ($properties) = ($sql =~ /^\s*SELECT\s(.*?)\sFROM\s/i); | ||||
752 | # $properties =~ s/[^\s]+\s*as\s*//ig; | ||||
753 | # $properties =~ s/\w+\.//g; | ||||
754 | # | ||||
755 | # $offset ||= 0; | ||||
756 | # my $position = ( $offset + $limit ); | ||||
757 | # | ||||
758 | # $sql = <<""; | ||||
759 | #SELECT $properties FROM ( | ||||
760 | # SELECT $properties, ROWNUM AS sqle_position FROM ( | ||||
761 | # $sql | ||||
762 | # ) | ||||
763 | #) | ||||
764 | #WHERE sqle_position > $offset AND sqle_position <= $position | ||||
765 | |||||
766 | |||||
767 | # | ||||
768 | # return ($sql, @params); | ||||
769 | #} | ||||
770 | |||||
771 | =item FetchFirst | ||||
772 | |||||
773 | =over 8 | ||||
774 | |||||
775 | =item Syntax | ||||
776 | |||||
777 | SELECT * FROM ( | ||||
778 | SELECT * FROM ( | ||||
779 | $sql | ||||
780 | ORDER BY order_cols_up | ||||
781 | FETCH FIRST $last ROWS ONLY | ||||
782 | ) foo | ||||
783 | ORDER BY order_cols_down | ||||
784 | FETCH FIRST $rows ROWS ONLY | ||||
785 | ) bar | ||||
786 | ORDER BY order_cols_up | ||||
787 | |||||
788 | =item Databases | ||||
789 | |||||
790 | IBM DB2 | ||||
791 | |||||
792 | =back | ||||
793 | |||||
794 | =cut | ||||
795 | |||||
796 | sub _FetchFirst { | ||||
797 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
798 | |||||
799 | my $last = $rows + $offset; | ||||
800 | |||||
801 | my ( $order_by_up, $order_by_down ) = $self->_order_directions( $order ); | ||||
802 | |||||
803 | $sql = <<""; | ||||
804 | SELECT * FROM ( | ||||
805 | SELECT * FROM ( | ||||
806 | $sql | ||||
807 | $order_by_up | ||||
808 | FETCH FIRST $last ROWS ONLY | ||||
809 | ) foo | ||||
810 | $order_by_down | ||||
811 | FETCH FIRST $rows ROWS ONLY | ||||
812 | ) bar | ||||
813 | $order_by_up | ||||
814 | |||||
815 | return $sql; | ||||
816 | } | ||||
817 | |||||
818 | =item GenericSubQ | ||||
819 | |||||
820 | When all else fails, this should work for many databases, but it is probably | ||||
821 | fairly slow. | ||||
822 | |||||
823 | This method relies on having a column with unique values as the first column in | ||||
824 | the C<SELECT> clause (i.e. the first column in the C<\@fields> parameter). The | ||||
825 | results will be sorted by that unique column, so any C<$order> parameter is | ||||
826 | ignored, unless it matches the unique column, in which case the direction of | ||||
827 | the sort is honoured. | ||||
828 | |||||
829 | =over 8 | ||||
830 | |||||
831 | =item Syntax | ||||
832 | |||||
833 | SELECT field_list FROM $table X WHERE where_clause AND | ||||
834 | ( | ||||
835 | SELECT COUNT(*) FROM $table WHERE $pk > X.$pk | ||||
836 | ) | ||||
837 | BETWEEN $offset AND $last | ||||
838 | ORDER BY $pk $asc_desc | ||||
839 | |||||
840 | C<$pk> is the first column in C<field_list>. | ||||
841 | |||||
842 | C<$asc_desc> is the opposite direction to that specified in the method call. So | ||||
843 | if you want the final results sorted C<ASC>, say so, and it gets flipped | ||||
844 | internally, but the results come out as you'd expect. I think. | ||||
845 | |||||
846 | The C<BETWEEN $offset AND $last> clause is replaced with C<E<lt> $rows> if | ||||
847 | <$offset == 0>. | ||||
848 | |||||
849 | =item Databases | ||||
850 | |||||
851 | Sybase | ||||
852 | Anything not otherwise known to this module. | ||||
853 | |||||
854 | =back | ||||
855 | |||||
856 | =cut | ||||
857 | |||||
858 | sub _GenericSubQ { | ||||
859 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
860 | |||||
861 | my $last = $rows + $offset; | ||||
862 | |||||
863 | my $order_by = $self->_order_by( $order ); | ||||
864 | |||||
865 | my ( $pk, $table ) = $sql =~ /^\s*SELECT\s+(\w+),?.*\sFROM\s+([\w]+)/i; | ||||
866 | |||||
867 | #warn "pk: $pk"; | ||||
868 | #warn "table: $table"; | ||||
869 | |||||
870 | # get specified sort order and swap it to get the expected output (I think?) | ||||
871 | my ( $asc_desc ) = $order_by =~ /\b$pk\s+(ASC|DESC)\s*/i; | ||||
872 | $asc_desc = uc( $asc_desc ) || 'ASC'; | ||||
873 | $asc_desc = $asc_desc eq 'ASC' ? 'DESC' : 'ASC'; | ||||
874 | |||||
875 | $sql =~ s/FROM $table /FROM $table X /; | ||||
876 | |||||
877 | my $limit = $offset ? "BETWEEN $offset AND $last" : "< $rows"; | ||||
878 | |||||
879 | $sql = <<""; | ||||
880 | $sql AND | ||||
881 | ( | ||||
882 | SELECT COUNT(*) FROM $table WHERE $pk > X.$pk | ||||
883 | ) | ||||
884 | $limit | ||||
885 | ORDER BY $pk $asc_desc | ||||
886 | |||||
887 | return $sql; | ||||
888 | } | ||||
889 | |||||
890 | |||||
891 | =begin notes | ||||
892 | |||||
893 | 1st page: | ||||
894 | |||||
895 | SELECT id, field1, fieldn | ||||
896 | FROM table_xyz X | ||||
897 | WHERE | ||||
898 | ( | ||||
899 | SELECT COUNT(*) FROM table_xyz WHERE id > X.id | ||||
900 | ) | ||||
901 | < 100 | ||||
902 | ORDER BY id DESC | ||||
903 | |||||
904 | Next page: | ||||
905 | |||||
906 | SELECT id, field1, fieldn | ||||
907 | FROM table_xyz X | ||||
908 | WHERE | ||||
909 | ( | ||||
910 | SELECT COUNT(*) FROM table_xyz WHERE id > X.id | ||||
911 | ) | ||||
912 | BETWEEN 100 AND 199 | ||||
913 | ORDER BY id DESC | ||||
914 | |||||
915 | |||||
916 | http://expertanswercenter.techtarget.com/eac/knowledgebaseAnswer/0,,sid63_gci978197,00.html | ||||
917 | |||||
918 | We can adapt the generic Top N query to this task. I would not use the generic | ||||
919 | method when TOP or LIMIT is available, but you're right, the previous answer | ||||
920 | is incomplete without this. | ||||
921 | |||||
922 | Using the same table and column names, the top 100 ids are given by: | ||||
923 | |||||
924 | SELECT id, field1, fieldn FROM table_xyz X | ||||
925 | WHERE ( SELECT COUNT(*) | ||||
926 | FROM table_xyz | ||||
927 | WHERE id > X.id ) < 100 | ||||
928 | ORDER BY id DESC | ||||
929 | |||||
930 | The subquery is correlated, which means that it will be evaluated for each row | ||||
931 | of the outer query. The subquery says "count the number of rows that have an | ||||
932 | id that is greater than this id." Note that the sort order is descending, so | ||||
933 | we are looking for ids that are greater, i.e. higher up in the result set. If | ||||
934 | that number is less than 100, then this row must be one of the top 100. Simple, | ||||
935 | eh? Unfortunately, it runs quite slowly. Furthermore, it takes ties into | ||||
936 | consideration, which is good, but this means that the number of rows returned | ||||
937 | isn't always going to be exactly 100 -- there will be extra rows if there are | ||||
938 | ties extending across the 100th place. | ||||
939 | |||||
940 | Next, we need the second set of 100: | ||||
941 | |||||
942 | select id | ||||
943 | , field1 | ||||
944 | , fieldn | ||||
945 | from table_xyz X | ||||
946 | where ( select count(*) | ||||
947 | from table_xyz | ||||
948 | where id > X.id ) between 100 and 199 | ||||
949 | order by id desc | ||||
950 | |||||
951 | See the pattern? Note that the same caveat applies about ties that extend | ||||
952 | across 200th place. | ||||
953 | |||||
954 | =end notes | ||||
955 | |||||
956 | =item First | ||||
957 | |||||
958 | =over 8 | ||||
959 | |||||
960 | =item Syntax | ||||
961 | |||||
962 | Looks to be identical to C<Top>, e.g. C<SELECT FIRST 10 * FROM table>. Can | ||||
963 | probably be implemented in a very similar way, but not done yet. | ||||
964 | |||||
965 | =item Databases | ||||
966 | |||||
967 | Informix | ||||
968 | |||||
969 | =back | ||||
970 | |||||
971 | =cut | ||||
972 | |||||
973 | sub _First { | ||||
974 | my ( $self, $sql, $order, $rows, $offset ) = @_; | ||||
975 | die 'FIRST not implemented'; | ||||
976 | |||||
977 | # fetch first 20 rows | ||||
978 | |||||
979 | # might need to add to regex in 'where' method | ||||
980 | |||||
981 | } | ||||
982 | |||||
983 | |||||
984 | 1 | 25µs | 1; | ||
985 | |||||
986 | __END__ | ||||
987 | |||||
988 | =back | ||||
989 | |||||
990 | =head1 SUBCLASSING | ||||
991 | |||||
992 | You can create your own syntax by making a subclass that provides an | ||||
993 | C<emulate_limit> method. This might be useful if you are using stored procedures | ||||
994 | to provide more efficient paging. | ||||
995 | |||||
996 | =over 4 | ||||
997 | |||||
998 | =item emulate_limit( $self, $sql, $order, $rows, $offset ) | ||||
999 | |||||
1000 | =over 4 | ||||
1001 | |||||
1002 | =item $sql | ||||
1003 | |||||
1004 | This is the SQL statement built by L<SQL::Abstract|SQL::Abstract>, but without | ||||
1005 | the ORDER BY clause, e.g. | ||||
1006 | |||||
1007 | SELECT foo, bar FROM my_table WHERE conditions | ||||
1008 | |||||
1009 | or just | ||||
1010 | |||||
1011 | WHERE conditions | ||||
1012 | |||||
1013 | if calling C<where> instead of C<select>. | ||||
1014 | |||||
1015 | =item $order | ||||
1016 | |||||
1017 | The C<order> parameter passed to the C<select> or C<where> call. You can get | ||||
1018 | an C<ORDER BY> clause from this by calling | ||||
1019 | |||||
1020 | my $order_by = $self->_order_by( $order ); | ||||
1021 | |||||
1022 | You can get a pair of C<ORDER BY> clauses that sort in opposite directions by | ||||
1023 | saying | ||||
1024 | |||||
1025 | my ( $up, $down ) = $self->_order_directions( $order ); | ||||
1026 | |||||
1027 | =back | ||||
1028 | |||||
1029 | The method should return a suitably modified SQL statement. | ||||
1030 | |||||
1031 | =back | ||||
1032 | |||||
1033 | =head1 AUTO-DETECTING THE DIALECT | ||||
1034 | |||||
1035 | The C<$dialect> parameter that can be passed to the constructor or to the | ||||
1036 | C<select> and C<where> methods can be a number of things. The module will | ||||
1037 | attempt to determine the appropriate syntax to use. | ||||
1038 | |||||
1039 | Supported C<$dialect> things are: | ||||
1040 | |||||
1041 | dialect name (e.g. LimitOffset, RowsTo, Top etc.) | ||||
1042 | database moniker (e.g. Oracle, SQLite etc.) | ||||
1043 | DBI database handle | ||||
1044 | Class::DBI subclass or object | ||||
1045 | |||||
1046 | =head1 CAVEATS | ||||
1047 | |||||
1048 | Paging results sets is a complicated undertaking, with several competing factors | ||||
1049 | to take into account. This module does B<not> magically give you the optimum | ||||
1050 | paging solution for your situation. It gives you a solution that may be good | ||||
1051 | enough in many situations. But if your tables are large, the SQL generated here | ||||
1052 | will often not be efficient. Or if your queries involve joins or other | ||||
1053 | complications, you will probably need to look elsewhere. | ||||
1054 | |||||
1055 | But if your tables aren't too huge, and your queries straightforward, you can | ||||
1056 | just plug this module in and move on to your next task. | ||||
1057 | |||||
1058 | =head1 ACKNOWLEDGEMENTS | ||||
1059 | |||||
1060 | Thanks to Aaron Johnson for the Top syntax model (SQL/Server and MS Access). | ||||
1061 | |||||
1062 | Thanks to Emanuele Zeppieri for the IBM DB2 syntax model. | ||||
1063 | |||||
1064 | =head1 TODO | ||||
1065 | |||||
1066 | Find more syntaxes to implement. | ||||
1067 | |||||
1068 | Test the syntaxes against real databases. I only have access to MySQL. Reports | ||||
1069 | of success or failure would be great. | ||||
1070 | |||||
1071 | =head1 DEPENDENCIES | ||||
1072 | |||||
1073 | L<SQL::Abstract|SQL::Abstract>, | ||||
1074 | L<DBI::Const::GetInfoType|DBI::Const::GetInfoType>, | ||||
1075 | L<Carp|Carp>. | ||||
1076 | |||||
1077 | =head1 SEE ALSO | ||||
1078 | |||||
1079 | L<DBIx::SQLEngine|DBIx::SQLEngine>, | ||||
1080 | L<DBIx::SearchBuilder|DBIx::SearchBuilder>, | ||||
1081 | L<DBIx::RecordSet|DBIx::RecordSet>. | ||||
1082 | |||||
1083 | =head1 BUGS | ||||
1084 | |||||
1085 | Please report all bugs via the CPAN Request Tracker at | ||||
1086 | L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=SQL-Abstract-Limit>. | ||||
1087 | |||||
1088 | =head1 COPYRIGHT AND LICENSE | ||||
1089 | |||||
1090 | Copyright 2004 by David Baird. | ||||
1091 | |||||
1092 | This library is free software; you can redistribute it and/or modify | ||||
1093 | it under the same terms as Perl itself. | ||||
1094 | |||||
1095 | =head1 AUTHOR | ||||
1096 | |||||
1097 | David Baird, C<cpan@riverside-cms.co.uk> | ||||
1098 | |||||
1099 | =head1 HOW IS IT DONE ELSEWHERE | ||||
1100 | |||||
1101 | A few CPAN modules do this for a few databases, but the most comprehensive | ||||
1102 | seem to be DBIx::SQLEngine, DBIx::SearchBuilder and DBIx::RecordSet. | ||||
1103 | |||||
1104 | Have a look in the source code for my notes on how these modules tackle | ||||
1105 | similar problems. | ||||
1106 | |||||
1107 | =begin notes | ||||
1108 | |||||
1109 | =over 4 | ||||
1110 | |||||
1111 | =item DBIx::SearchBuilder::Handle::Oracle | ||||
1112 | |||||
1113 | Transform an SQL query from: | ||||
1114 | |||||
1115 | SELECT main.* | ||||
1116 | FROM Tickets main | ||||
1117 | WHERE ((main.EffectiveId = main.id)) | ||||
1118 | AND ((main.Type = 'ticket')) | ||||
1119 | AND ( ( (main.Status = 'new')OR(main.Status = 'open') ) | ||||
1120 | AND ( (main.Queue = '1') ) ) | ||||
1121 | |||||
1122 | to: | ||||
1123 | |||||
1124 | SELECT * FROM ( | ||||
1125 | SELECT limitquery.*,rownum limitrownum FROM ( | ||||
1126 | SELECT main.* | ||||
1127 | FROM Tickets main | ||||
1128 | WHERE ((main.EffectiveId = main.id)) | ||||
1129 | AND ((main.Type = 'ticket')) | ||||
1130 | AND ( ( (main.Status = 'new')OR(main.Status = 'open') ) | ||||
1131 | AND ( (main.Queue = '1') ) ) | ||||
1132 | ) limitquery WHERE rownum <= 50 | ||||
1133 | ) WHERE limitrownum >= 1 | ||||
1134 | |||||
1135 | if ($per_page) { | ||||
1136 | # Oracle orders from 1 not zero | ||||
1137 | $first++; | ||||
1138 | # Make current query a sub select | ||||
1139 | $$statementref = "SELECT * FROM ( SELECT limitquery.*,rownum limitrownum FROM ( $$statementref ) limitquery WHERE rownum <= " . ($first + $per_page - 1) . " ) WHERE limitrownum >= " . $first; | ||||
1140 | } | ||||
1141 | |||||
1142 | =item DBIx::SQLEngine::Driver | ||||
1143 | |||||
1144 | sub sql_limit { | ||||
1145 | my $self = shift; | ||||
1146 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1147 | |||||
1148 | $sql .= " limit $limit" if $limit; | ||||
1149 | $sql .= " offset $offset" if $offset; | ||||
1150 | |||||
1151 | return ($sql, @params); | ||||
1152 | } | ||||
1153 | |||||
1154 | =item DBIx::SQLEngine::Driver::AnyData | ||||
1155 | |||||
1156 | Also: | ||||
1157 | |||||
1158 | DBIx::SQLEngine::Driver::CSV | ||||
1159 | |||||
1160 | Adds support for SQL select limit clause. | ||||
1161 | |||||
1162 | TODO: Needs workaround to support offset. | ||||
1163 | |||||
1164 | sub sql_limit { | ||||
1165 | my $self = shift; | ||||
1166 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1167 | |||||
1168 | # You can't apply "limit" to non-table fetches | ||||
1169 | $sql .= " limit $limit" if ( $sql =~ / from / ); | ||||
1170 | |||||
1171 | return ($sql, @params); | ||||
1172 | } | ||||
1173 | |||||
1174 | =item DBIx::SQLEngine::Driver::Informix - Support DBD::Informix and DBD::ODBC/Informix | ||||
1175 | |||||
1176 | =item sql_limit() | ||||
1177 | |||||
1178 | Not yet supported. Perhaps we should use "first $maxrows" and throw out the first $offset? | ||||
1179 | |||||
1180 | =back | ||||
1181 | |||||
1182 | =cut | ||||
1183 | |||||
1184 | sub sql_limit { | ||||
1185 | confess("Not yet supported") | ||||
1186 | } | ||||
1187 | |||||
1188 | =item DBIx::SQLEngine::Driver::MSSQL - Support DBD::ODBC with Microsoft SQL Server | ||||
1189 | |||||
1190 | =item sql_limit() | ||||
1191 | |||||
1192 | Adds support for SQL select limit clause. | ||||
1193 | |||||
1194 | =back | ||||
1195 | |||||
1196 | =cut | ||||
1197 | |||||
1198 | sub sql_limit { | ||||
1199 | my $self = shift; | ||||
1200 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1201 | |||||
1202 | # You can't apply "limit" to non-table fetches like "select LAST_INSERT_ID" | ||||
1203 | if ( $sql =~ /\bfrom\b/ and defined $limit or defined $offset) { | ||||
1204 | $sql .= " limit $limit" if $limit; | ||||
1205 | $sql .= " offset $offset" if $offset; | ||||
1206 | } | ||||
1207 | |||||
1208 | return ($sql, @params); | ||||
1209 | } | ||||
1210 | |||||
1211 | |||||
1212 | |||||
1213 | =item DBIx::SQLEngine::Driver::Mysql - Support DBD::mysql | ||||
1214 | |||||
1215 | =item sql_limit() | ||||
1216 | |||||
1217 | Adds support for SQL select limit clause. | ||||
1218 | |||||
1219 | =back | ||||
1220 | |||||
1221 | =cut | ||||
1222 | |||||
1223 | sub sql_limit { | ||||
1224 | my $self = shift; | ||||
1225 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1226 | |||||
1227 | # You can't apply "limit" to non-table fetches like "select LAST_INSERT_ID" | ||||
1228 | if ( $sql =~ /\bfrom\b/ and $limit or $offset) { | ||||
1229 | $limit ||= 1_000_000; # MySQL select with offset requires a limit | ||||
1230 | $sql .= " limit " . ( $offset ? "$offset," : '' ) . $limit; | ||||
1231 | } | ||||
1232 | |||||
1233 | return ($sql, @params); | ||||
1234 | } | ||||
1235 | |||||
1236 | =item DBIx::SQLEngine::Driver::Oracle - Support DBD::Oracle and DBD::ODBC/Oracle | ||||
1237 | |||||
1238 | =item sql_limit() | ||||
1239 | |||||
1240 | Adds support for SQL select limit clause. | ||||
1241 | |||||
1242 | Implemented as a subselect with ROWNUM. | ||||
1243 | |||||
1244 | =back | ||||
1245 | |||||
1246 | =cut | ||||
1247 | |||||
1248 | sub sql_limit { | ||||
1249 | my $self = shift; | ||||
1250 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1251 | |||||
1252 | # remove tablealiases and group-functions from outer query properties | ||||
1253 | my ($properties) = ($sql =~ /^\s*SELECT\s(.*?)\sFROM\s/i); | ||||
1254 | $properties =~ s/[^\s]+\s*as\s*//ig; | ||||
1255 | $properties =~ s/\w+\.//g; | ||||
1256 | |||||
1257 | $offset ||= 0; | ||||
1258 | my $position = ( $offset + $limit ); | ||||
1259 | |||||
1260 | $sql = <<""; | ||||
1261 | SELECT $properties FROM ( | ||||
1262 | SELECT $properties, ROWNUM AS sqle_position FROM ( | ||||
1263 | $sql | ||||
1264 | ) | ||||
1265 | ) | ||||
1266 | WHERE sqle_position > $offset AND sqle_position <= $position | ||||
1267 | |||||
1268 | return ($sql, @params); | ||||
1269 | } | ||||
1270 | |||||
1271 | =item DBIx::SQLEngine::Driver::Pg - Support DBD::Pg | ||||
1272 | |||||
1273 | =head2 sql_limit | ||||
1274 | |||||
1275 | $sqldb->sql_limit( $limit, $offset, $sql, @params ) : $sql, @params | ||||
1276 | |||||
1277 | Adds support for SQL select limit clause. | ||||
1278 | |||||
1279 | =cut | ||||
1280 | |||||
1281 | sub sql_limit { | ||||
1282 | my $self = shift; | ||||
1283 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1284 | |||||
1285 | # You can't apply "limit" to non-table fetches like "select LAST_INSERT_ID" | ||||
1286 | if ( $sql =~ /\bfrom\b/ and defined $limit or defined $offset) { | ||||
1287 | $sql .= " limit $limit" if $limit; | ||||
1288 | $sql .= " offset $offset" if $offset; | ||||
1289 | } | ||||
1290 | |||||
1291 | return ($sql, @params); | ||||
1292 | } | ||||
1293 | |||||
1294 | =item DBIx::SQLEngine::Driver::SQLite - Support DBD::SQLite driver | ||||
1295 | |||||
1296 | =head2 sql_limit | ||||
1297 | |||||
1298 | Adds support for SQL select limit clause. | ||||
1299 | |||||
1300 | =cut | ||||
1301 | |||||
1302 | sub sql_limit { | ||||
1303 | my $self = shift; | ||||
1304 | my ( $limit, $offset, $sql, @params ) = @_; | ||||
1305 | |||||
1306 | # You can't apply "limit" to non-table fetches like "select LAST_INSERT_ID" | ||||
1307 | if ( $sql =~ /\bfrom\b/ and defined $limit or defined $offset) { | ||||
1308 | $sql .= " limit $limit" if $limit; | ||||
1309 | $sql .= " offset $offset" if $offset; | ||||
1310 | } | ||||
1311 | |||||
1312 | return ($sql, @params); | ||||
1313 | } | ||||
1314 | |||||
1315 | =item DBIx::SQLEngine::Driver::Sybase - Extends SQLEngine for DBMS Idiosyncrasies | ||||
1316 | |||||
1317 | =item sql_limit() | ||||
1318 | |||||
1319 | Not yet supported. | ||||
1320 | |||||
1321 | See http://www.isug.com/Sybase_FAQ/ASE/section6.2.html#6.2.12 | ||||
1322 | |||||
1323 | =back | ||||
1324 | |||||
1325 | =cut | ||||
1326 | |||||
1327 | sub sql_limit { | ||||
1328 | confess("Not yet supported") | ||||
1329 | } | ||||
1330 | |||||
1331 | |||||
1332 | =item DBIx::SQLEngine::Driver::Sybase::MSSQL - Support DBD::Sybase with Microsoft SQL | ||||
1333 | |||||
1334 | Nothing. | ||||
1335 | |||||
1336 | =back | ||||
1337 | |||||
1338 | =cut | ||||
1339 | |||||
1340 | =end notes |