The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#----------------------------------------------------------------------
package DBIx::DataModel::Statement::Oracle;
#----------------------------------------------------------------------
use strict;
use warnings;
no strict 'refs';

use parent      qw/DBIx::DataModel::Statement/;
use mro         qw/c3/;
use DBD::Oracle 1.62 qw/:ora_fetch_orient :ora_exe_modes/;
use Carp;

sub sqlize {
  my ($self, @args) = @_;

  # merge new args into $self->{args}
  $self->refine(@args) if @args;

  # reorganize pagination (code copied from SQL::Abstract::More), in order
  # to capture all cases of limit/offset
  my $page_index = delete $self->{args}{-page_index};
  my $page_size  = delete $self->{args}{-page_size};
  if ($page_index || $page_size) {
    not exists $self->{args}{$_} or croak "-page_size conflicts with $_"
      for qw/-limit -offset/;
    $self->{args}{-limit} = $page_size;
    if ($page_index) {
      $self->{args}{-offset} = ($page_index - 1) * $page_size;
    }
  }

  # remove -limit and -offset from args; they will be handled later in
  # prepare() and execute(), see below
  $self->{_ora_limit}  = delete $self->{args}{-limit};
  $self->{offset}      = delete $self->{args}{-offset};

  # if there is an offset, we must ask for a scrollable statement
  $self->refine(-prepare_attrs => 
                  {ora_exe_mode => OCI_STMT_SCROLLABLE_READONLY})
     if $self->{offset};

  return $self->next::method();
}


# recompute page size/index from limit/offset
sub page_size   { my $self = shift;
                  $self->{_ora_limit}  || POSIX::LONG_MAX            }
sub page_index  { my $self = shift;
                  int(($self->{offset} || 0) / $self->page_size) + 1 }



sub execute {
  my ($self, @args) = @_;
  $self->next::method(@args);

  # go to initial offset, if needed 
  if ($self->{offset}) {
    $self->{sth}->ora_fetch_scroll(OCI_FETCH_ABSOLUTE, $self->{offset});
    $self->{row_num} = $self->{offset};
  }
  return $self;
}

sub next {
  my ($self, $n_rows) = @_;

  # first execute the statement
  $self->execute if $self->{status} < DBIx::DataModel::Statement::EXECUTED;

  # if needed, ajust $n_rows according to requested limit
  $n_rows = $self->{_ora_limit} if defined $self->{_ora_limit};

  # now regular handling can do the rest of the job;
  return $self->next::method($n_rows);
}

1;

__END__

=head1 NAME

DBIx::DataModel::Statement::Oracle - Statement for interacting with DBD::Oracle

=head1 SYNOPSIS

  DBIx::DataModel->Schema("MySchema",
     statement_class => "DBIx::DataModel::Statement::Oracle",
  );

  my $rows = $source->select(
    ...,
    -limit  => 50,
    -offset => 200,
  );

=head1 DESCRIPTION

This subclass redefines some parent methods 
from L<DBIx::DataModel::Statement> in order to take advantage
of L<DBD::Oracle/"Scrollable Cursor Methods">.

This is interesting for applications that need to do pagination
within result sets, because Oracle has no support for LIMIT/OFFSET in SQL.
So here we use some special methods of the Oracle driver to jump
to a specific row within a resultset, and then extract a limited
number of rows.

The API is exactly the same as other, regular DBIx::DataModel implementations.

=head1 AUTHOR

Laurent Dami, E<lt>laurent.dami AT etat  ge  chE<gt>, April 2012.

=head1 COPYRIGHT AND LICENSE

Copyright 2011 by Laurent Dami.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.