Merge branch 'people/riba/retire_sqla_limit'
Peter Rabbitson [Fri, 27 Aug 2010 10:58:15 +0000 (12:58 +0200)]
44 files changed:
Changes
Makefile.PL
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/SQLMaker.pm [new file with mode: 0644]
lib/DBIx/Class/SQLMaker/LimitDialects.pm [moved from lib/DBIx/Class/SQLAHacks.pm with 55% similarity]
lib/DBIx/Class/SQLMaker/MSSQL.pm [moved from lib/DBIx/Class/SQLAHacks/MSSQL.pm with 72% similarity]
lib/DBIx/Class/SQLMaker/MySQL.pm [moved from lib/DBIx/Class/SQLAHacks/MySQL.pm with 90% similarity]
lib/DBIx/Class/SQLMaker/Oracle.pm [moved from lib/DBIx/Class/SQLAHacks/Oracle.pm with 93% similarity]
lib/DBIx/Class/SQLMaker/OracleJoins.pm [moved from lib/DBIx/Class/SQLAHacks/OracleJoins.pm with 92% similarity]
lib/DBIx/Class/SQLMaker/SQLite.pm [moved from lib/DBIx/Class/SQLAHacks/SQLite.pm with 82% similarity]
lib/DBIx/Class/Storage.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/DB2.pm
lib/DBIx/Class/Storage/DBI/Informix.pm
lib/DBIx/Class/Storage/DBI/InterBase.pm
lib/DBIx/Class/Storage/DBI/MSSQL.pm
lib/DBIx/Class/Storage/DBI/ODBC/ACCESS.pm
lib/DBIx/Class/Storage/DBI/ODBC/DB2_400_SQL.pm
lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm
lib/DBIx/Class/Storage/DBI/Oracle/WhereJoins.pm
lib/DBIx/Class/Storage/DBI/Pg.pm
lib/DBIx/Class/Storage/DBI/Replicated.pm
lib/DBIx/Class/Storage/DBI/SQLAnywhere.pm
lib/DBIx/Class/Storage/DBI/SQLite.pm
lib/DBIx/Class/Storage/DBI/Sybase/ASE.pm
lib/DBIx/Class/Storage/DBI/mysql.pm
t/71mysql.t
t/72pg.t
t/74mssql.t
t/76joins.t
t/sqlmaker/core.t [moved from t/sqlahacks/sql_maker/sql_maker.t with 96% similarity]
t/sqlmaker/core_quoted.t [moved from t/sqlahacks/sql_maker/sql_maker_quote.t with 100% similarity]
t/sqlmaker/limit_dialects/custom.t [new file with mode: 0644]
t/sqlmaker/limit_dialects/generic_subq.t [moved from t/sqlahacks/limit_dialects/generic_subq.t with 100% similarity]
t/sqlmaker/limit_dialects/rno.t [moved from t/sqlahacks/limit_dialects/rno.t with 100% similarity]
t/sqlmaker/limit_dialects/rownum.t [moved from t/sqlahacks/limit_dialects/rownum.t with 100% similarity]
t/sqlmaker/limit_dialects/toplimit.t [moved from t/sqlahacks/limit_dialects/toplimit.t with 100% similarity]
t/sqlmaker/oracle.t [moved from t/sqlahacks/oracle.t with 94% similarity]
t/sqlmaker/oraclejoin.t [moved from t/sqlahacks/oraclejoin.t with 95% similarity]
t/sqlmaker/order_by_func.t [moved from t/sqlahacks/order_by_func.t with 100% similarity]
t/sqlmaker/quotes/quotes.t [moved from t/sqlahacks/quotes/quotes.t with 100% similarity]
t/sqlmaker/quotes/quotes_newstyle.t [moved from t/sqlahacks/quotes/quotes_newstyle.t with 100% similarity]
t/sqlmaker/sqlite.t [moved from t/sqlahacks/sqlite.t with 100% similarity]
xt/podcoverage.t

diff --git a/Changes b/Changes
index e9e8654..d7ffe6a 100644 (file)
--- a/Changes
+++ b/Changes
@@ -29,6 +29,8 @@ Revision history for DBIx::Class
         - Bumped minimum Module::Install for developers
         - Bumped DBD::SQLite dependency and removed some TODO markers
           from tests
+        - No longer depend on SQL::Abstract::Limit - DBIC has been doing
+          most of the heavy lifting for a while anyway
 
 0.08123 2010-06-12 14:46 (UTC)
     * Fixes
index fad044a..ce27a84 100644 (file)
@@ -57,7 +57,6 @@ my $runtime_requires = {
   'Module::Find'             => '0.06',
   'Path::Class'              => '0.18',
   'SQL::Abstract'            => '1.67',
-  'SQL::Abstract::Limit'     => '0.13',
   'Sub::Name'                => '0.04',
   'Data::Dumper::Concise'    => '1.000',
   'Scope::Guard'             => '0.03',
@@ -222,15 +221,13 @@ resources 'bugtracker'  => 'http://rt.cpan.org/NoAuth/Bugs.html?Dist=DBIx-Class'
 # Deprecated/internal modules need no exposure
 no_index directory => $_ for (qw|
   lib/DBIx/Class/Admin
-  lib/DBIx/Class/SQLAHacks
   lib/DBIx/Class/PK/Auto
   lib/DBIx/Class/CDBICompat
 |);
 no_index package => $_ for (qw/
-  DBIx::Class::SQLAHacks DBIx::Class::Storage::DBIHacks
+  DBIx::Class::Storage::DBIHacks
 /);
 
-
 WriteAll();
 
 # Re-write META.yml to _exclude_ all forced requires (we do not want to ship this)
index 72dba57..3e3f5db 100644 (file)
@@ -1695,24 +1695,6 @@ this, you can also overload the C<connection> method for your schema class:
      return $rv;
  }
 
-=head2 Setting limit dialect for SQL::Abstract::Limit
-
-In some cases, SQL::Abstract::Limit cannot determine the dialect of
-the remote SQL server by looking at the database handle. This is a
-common problem when using the DBD::JDBC, since the DBD-driver only
-know that in has a Java-driver available, not which JDBC driver the
-Java component has loaded.  This specifically sets the limit_dialect
-to Microsoft SQL-server (See more names in SQL::Abstract::Limit
--documentation.
-
-  __PACKAGE__->storage->sql_maker->limit_dialect('mssql');
-
-The JDBC bridge is one way of getting access to a MSSQL server from a platform
-that Microsoft doesn't deliver native client libraries for. (e.g. Linux)
-
-The limit dialect can also be set at connect time by specifying a
-C<limit_dialect> key in the final hash as shown above.
-
 =head2 Working with PostgreSQL array types
 
 You can also assign values to PostgreSQL array columns by passing array
diff --git a/lib/DBIx/Class/SQLMaker.pm b/lib/DBIx/Class/SQLMaker.pm
new file mode 100644 (file)
index 0000000..d0fbc77
--- /dev/null
@@ -0,0 +1,366 @@
+package DBIx::Class::SQLMaker;
+
+=head1 NAME
+
+DBIx::Class::SQLMaker - An SQL::Abstract-based SQL maker class
+
+=head1 DESCRIPTION
+
+This module is a subclass of L<SQL::Abstract> and includes a number of
+DBIC-specific workarounds, not yet suitable for inclusion into the
+L<SQL::Abstract> core. It also provides all (and more than) the functionality
+of L<SQL::Abstract::Limit>, see L<DBIx::Class::SQLMaker::LimitDialects> for
+more info.
+
+Currently the enhancements to L<SQL::Abstract> are:
+
+=over
+
+=item * Support for C<JOIN> statements (via extended C<table/from> support)
+
+=item * Support of functions in C<SELECT> lists
+
+=item * C<GROUP BY>/C<HAVING> support (via extensions to the order_by parameter)
+
+=item * Support of C<...FOR UPDATE> type of select statement modifiers
+
+=back
+
+=cut
+
+use base qw/
+  DBIx::Class::SQLMaker::LimitDialects
+  SQL::Abstract
+  Class::Accessor::Grouped
+/;
+use mro 'c3';
+use strict;
+use warnings;
+use Sub::Name 'subname';
+use Carp::Clan qw/^DBIx::Class|^SQL::Abstract|^Try::Tiny/;
+use namespace::clean;
+
+__PACKAGE__->mk_group_accessors (simple => qw/quote_char name_sep limit_dialect/);
+
+BEGIN {
+  # reinstall the carp()/croak() functions imported into SQL::Abstract
+  # as Carp and Carp::Clan do not like each other much
+  no warnings qw/redefine/;
+  no strict qw/refs/;
+  for my $f (qw/carp croak/) {
+
+    my $orig = \&{"SQL::Abstract::$f"};
+    my $clan_import = \&{$f};
+    *{"SQL::Abstract::$f"} = subname "SQL::Abstract::$f" =>
+      sub {
+        if (Carp::longmess() =~ /DBIx::Class::SQLMaker::[\w]+ .+? called \s at/x) {
+          $clan_import->(@_);
+        }
+        else {
+          goto $orig;
+        }
+      };
+  }
+}
+
+# the "oh noes offset/top without limit" constant
+# limited to 32 bits for sanity (and consistency,
+# since it is ultimately handed to sprintf %u)
+# Implemented as a method, since ::Storage::DBI also
+# refers to it (i.e. for the case of software_limit or
+# as the value to abuse with MSSQL ordered subqueries)
+sub __max_int { 0xFFFFFFFF };
+
+# Handle limit-dialect selection
+sub select {
+  my ($self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
+
+
+  $fields = $self->_recurse_fields($fields);
+
+  if (defined $offset) {
+    croak ('A supplied offset must be a non-negative integer')
+      if ( $offset =~ /\D/ or $offset < 0 );
+  }
+  $offset ||= 0;
+
+  if (defined $limit) {
+    croak ('A supplied limit must be a positive integer')
+      if ( $limit =~ /\D/ or $limit <= 0 );
+  }
+  elsif ($offset) {
+    $limit = $self->__max_int;
+  }
+
+
+  my ($sql, @bind);
+  if ($limit) {
+    # this is legacy code-flow from SQLA::Limit, it is not set in stone
+
+    ($sql, @bind) = $self->next::method ($table, $fields, $where);
+
+    my $limiter =
+      $self->can ('emulate_limit')  # also backcompat hook from SQLA::Limit
+        ||
+      do {
+        my $dialect = $self->limit_dialect
+          or croak "Unable to generate SQL-limit - no limit dialect specified on $self, and no emulate_limit method found";
+        $self->can ("_$dialect")
+          or croak (__PACKAGE__ . " does not implement the requested dialect '$dialect'");
+      }
+    ;
+
+    $sql = $self->$limiter ($sql, $rs_attrs, $limit, $offset);
+  }
+  else {
+    ($sql, @bind) = $self->next::method ($table, $fields, $where, $rs_attrs);
+  }
+
+  push @{$self->{where_bind}}, @bind;
+
+# this *must* be called, otherwise extra binds will remain in the sql-maker
+  my @all_bind = $self->_assemble_binds;
+
+  return wantarray ? ($sql, @all_bind) : $sql;
+}
+
+sub _assemble_binds {
+  my $self = shift;
+  return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/from where having order/);
+}
+
+# Handle default inserts
+sub insert {
+# optimized due to hotttnesss
+#  my ($self, $table, $data, $options) = @_;
+
+  # SQLA will emit INSERT INTO $table ( ) VALUES ( )
+  # which is sadly understood only by MySQL. Change default behavior here,
+  # until SQLA2 comes with proper dialect support
+  if (! $_[2] or (ref $_[2] eq 'HASH' and !keys %{$_[2]} ) ) {
+    my $sql = "INSERT INTO $_[1] DEFAULT VALUES";
+
+    if (my $ret = ($_[3]||{})->{returning} ) {
+      $sql .= $_[0]->_insert_returning ($ret);
+    }
+
+    return $sql;
+  }
+
+  next::method(@_);
+}
+
+sub _recurse_fields {
+  my ($self, $fields) = @_;
+  my $ref = ref $fields;
+  return $self->_quote($fields) unless $ref;
+  return $$fields if $ref eq 'SCALAR';
+
+  if ($ref eq 'ARRAY') {
+    return join(', ', map { $self->_recurse_fields($_) } @$fields);
+  }
+  elsif ($ref eq 'HASH') {
+    my %hash = %$fields;  # shallow copy
+
+    my $as = delete $hash{-as};   # if supplied
+
+    my ($func, $args, @toomany) = %hash;
+
+    # there should be only one pair
+    if (@toomany) {
+      croak "Malformed select argument - too many keys in hash: " . join (',', keys %$fields );
+    }
+
+    if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
+      croak (
+        'The select => { distinct => ... } syntax is not supported for multiple columns.'
+       .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
+       .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
+      );
+    }
+
+    my $select = sprintf ('%s( %s )%s',
+      $self->_sqlcase($func),
+      $self->_recurse_fields($args),
+      $as
+        ? sprintf (' %s %s', $self->_sqlcase('as'), $self->_quote ($as) )
+        : ''
+    );
+
+    return $select;
+  }
+  # Is the second check absolutely necessary?
+  elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
+    return $self->_fold_sqlbind( $fields );
+  }
+  else {
+    croak($ref . qq{ unexpected in _recurse_fields()})
+  }
+}
+
+my $for_syntax = {
+  update => 'FOR UPDATE',
+  shared => 'FOR SHARE',
+};
+
+# this used to be a part of _order_by but is broken out for clarity.
+# What we have been doing forever is hijacking the $order arg of
+# SQLA::select to pass in arbitrary pieces of data (first the group_by,
+# then pretty much the entire resultset attr-hash, as more and more
+# things in the SQLA space need to have mopre info about the $rs they
+# create SQL for. The alternative would be to keep expanding the
+# signature of _select with more and more positional parameters, which
+# is just gross. All hail SQLA2!
+sub _parse_rs_attrs {
+  my ($self, $arg) = @_;
+
+  my $sql = '';
+
+  if (my $g = $self->_recurse_fields($arg->{group_by}) ) {
+    $sql .= $self->_sqlcase(' group by ') . $g;
+  }
+
+  if (defined $arg->{having}) {
+    my ($frag, @bind) = $self->_recurse_where($arg->{having});
+    push(@{$self->{having_bind}}, @bind);
+    $sql .= $self->_sqlcase(' having ') . $frag;
+  }
+
+  if (defined $arg->{order_by}) {
+    $sql .= $self->_order_by ($arg->{order_by});
+  }
+
+  if (my $for = $arg->{for}) {
+    $sql .= " $for_syntax->{$for}" if $for_syntax->{$for};
+  }
+
+  return $sql;
+}
+
+sub _order_by {
+  my ($self, $arg) = @_;
+
+  # check that we are not called in legacy mode (order_by as 4th argument)
+  if (ref $arg eq 'HASH' and not grep { $_ =~ /^-(?:desc|asc)/i } keys %$arg ) {
+    return $self->_parse_rs_attrs ($arg);
+  }
+  else {
+    my ($sql, @bind) = $self->next::method($arg);
+    push @{$self->{order_bind}}, @bind;
+    return $sql;
+  }
+}
+
+sub _table {
+# optimized due to hotttnesss
+#  my ($self, $from) = @_;
+  if (my $ref = ref $_[1] ) {
+    if ($ref eq 'ARRAY') {
+      return $_[0]->_recurse_from(@{$_[1]});
+    }
+    elsif ($ref eq 'HASH') {
+      return $_[0]->_make_as($_[1]);
+    }
+  }
+
+  return $_[0]->next::method ($_[1]);
+}
+
+sub _generate_join_clause {
+    my ($self, $join_type) = @_;
+
+    return sprintf ('%s JOIN ',
+      $join_type ?  ' ' . uc($join_type) : ''
+    );
+}
+
+sub _recurse_from {
+  my ($self, $from, @join) = @_;
+  my @sqlf;
+  push(@sqlf, $self->_make_as($from));
+  foreach my $j (@join) {
+    my ($to, $on) = @$j;
+
+
+    # check whether a join type exists
+    my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
+    my $join_type;
+    if (ref($to_jt) eq 'HASH' and defined($to_jt->{-join_type})) {
+      $join_type = $to_jt->{-join_type};
+      $join_type =~ s/^\s+ | \s+$//xg;
+    }
+
+    $join_type = $self->{_default_jointype} if not defined $join_type;
+
+    push @sqlf, $self->_generate_join_clause( $join_type );
+
+    if (ref $to eq 'ARRAY') {
+      push(@sqlf, '(', $self->_recurse_from(@$to), ')');
+    } else {
+      push(@sqlf, $self->_make_as($to));
+    }
+    push(@sqlf, ' ON ', $self->_join_condition($on));
+  }
+  return join('', @sqlf);
+}
+
+sub _fold_sqlbind {
+  my ($self, $sqlbind) = @_;
+
+  my @sqlbind = @$$sqlbind; # copy
+  my $sql = shift @sqlbind;
+  push @{$self->{from_bind}}, @sqlbind;
+
+  return $sql;
+}
+
+sub _make_as {
+  my ($self, $from) = @_;
+  return join(' ', map { (ref $_ eq 'SCALAR' ? $$_
+                        : ref $_ eq 'REF'    ? $self->_fold_sqlbind($_)
+                        : $self->_quote($_))
+                       } reverse each %{$self->_skip_options($from)});
+}
+
+sub _skip_options {
+  my ($self, $hash) = @_;
+  my $clean_hash = {};
+  $clean_hash->{$_} = $hash->{$_}
+    for grep {!/^-/} keys %$hash;
+  return $clean_hash;
+}
+
+sub _join_condition {
+  my ($self, $cond) = @_;
+  if (ref $cond eq 'HASH') {
+    my %j;
+    for (keys %$cond) {
+      my $v = $cond->{$_};
+      if (ref $v) {
+        croak (ref($v) . qq{ reference arguments are not supported in JOINS - try using \"..." instead'})
+            if ref($v) ne 'SCALAR';
+        $j{$_} = $v;
+      }
+      else {
+        my $x = '= '.$self->_quote($v); $j{$_} = \$x;
+      }
+    };
+    return scalar($self->_recurse_where(\%j));
+  } elsif (ref $cond eq 'ARRAY') {
+    return join(' OR ', map { $self->_join_condition($_) } @$cond);
+  } else {
+    croak "Can't handle this yet!";
+  }
+}
+
+1;
+
+=head1 AUTHORS
+
+See L<DBIx::Class/CONTRIBUTORS>.
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
+
+=cut
similarity index 55%
rename from lib/DBIx/Class/SQLAHacks.pm
rename to lib/DBIx/Class/SQLMaker/LimitDialects.pm
index 3cf750c..d4689a7 100644 (file)
-package # Hide from PAUSE
-  DBIx::Class::SQLAHacks;
+package DBIx::Class::SQLMaker::LimitDialects;
 
-# This module is a subclass of SQL::Abstract::Limit and includes a number
-# of DBIC-specific workarounds, not yet suitable for inclusion into the
-# SQLA core
-
-use base qw/SQL::Abstract::Limit/;
-use strict;
 use warnings;
+use strict;
+
+use Carp::Clan qw/^DBIx::Class|^SQL::Abstract|^Try::Tiny/;
 use List::Util 'first';
-use Sub::Name 'subname';
 use namespace::clean;
-use Carp::Clan qw/^DBIx::Class|^SQL::Abstract|^Try::Tiny/;
 
-BEGIN {
-  # reinstall the carp()/croak() functions imported into SQL::Abstract
-  # as Carp and Carp::Clan do not like each other much
-  no warnings qw/redefine/;
-  no strict qw/refs/;
-  for my $f (qw/carp croak/) {
-
-    my $orig = \&{"SQL::Abstract::$f"};
-    *{"SQL::Abstract::$f"} = subname "SQL::Abstract::$f" =>
-      sub {
-        if (Carp::longmess() =~ /DBIx::Class::SQLAHacks::[\w]+ .+? called \s at/x) {
-          __PACKAGE__->can($f)->(@_);
-        }
-        else {
-          goto $orig;
-        }
-      };
+# FIXME
+# This dialect has not been ported to the subquery-realiasing code
+# that all other subquerying dialects are using. It is very possible
+# that this dialect is entirely unnecessary - it is currently only
+# used by ::Storage::DBI::ODBC::DB2_400_SQL which *should* be able to
+# just subclass ::Storage::DBI::DB2 and use the already rewritten
+# RowNumberOver. However nobody has access to this specific database
+# engine, thus keeping legacy code as-is
+# IF someone ever manages to test DB2-AS/400 with RNO, all the code
+# in this block should go on to meet its maker
+{
+  sub _FetchFirst {
+    my ( $self, $sql, $order, $rows, $offset ) = @_;
+
+    my $last = $rows + $offset;
+
+    my ( $order_by_up, $order_by_down ) = $self->_order_directions( $order );
+
+    $sql = "
+      SELECT * FROM (
+        SELECT * FROM (
+          $sql
+          $order_by_up
+          FETCH FIRST $last ROWS ONLY
+        ) foo
+        $order_by_down
+        FETCH FIRST $rows ROWS ONLY
+      ) bar
+      $order_by_up
+    ";
+
+    return $sql;
   }
-}
 
-# the "oh noes offset/top without limit" constant
-# limited to 32 bits for sanity (and since it is fed
-# to sprintf %u)
-sub __max_int { 0xFFFFFFFF };
+  sub _order_directions {
+    my ( $self, $order ) = @_;
 
+    return unless $order;
 
-# Tries to determine limit dialect.
-#
-sub new {
-  my $self = shift->SUPER::new(@_);
+    my $ref = ref $order;
 
-  # This prevents the caching of $dbh in S::A::L, I believe
-  # If limit_dialect is a ref (like a $dbh), go ahead and replace
-  #   it with what it resolves to:
-  $self->{limit_dialect} = $self->_find_syntax($self->{limit_dialect})
-    if ref $self->{limit_dialect};
+    my @order;
 
-  $self;
+    CASE: {
+      @order = @$order,     last CASE if $ref eq 'ARRAY';
+      @order = ( $order ),  last CASE unless $ref;
+      @order = ( $$order ), last CASE if $ref eq 'SCALAR';
+      croak __PACKAGE__ . ": Unsupported data struct $ref for ORDER BY";
+    }
+
+    my ( $order_by_up, $order_by_down );
+
+    foreach my $spec ( @order )
+    {
+        my @spec = split ' ', $spec;
+        croak( "bad column order spec: $spec" ) if @spec > 2;
+        push( @spec, 'ASC' ) unless @spec == 2;
+        my ( $col, $up ) = @spec; # or maybe down
+        $up = uc( $up );
+        croak( "bad direction: $up" ) unless $up =~ /^(?:ASC|DESC)$/;
+        $order_by_up .= ", $col $up";
+        my $down = $up eq 'ASC' ? 'DESC' : 'ASC';
+        $order_by_down .= ", $col $down";
+    }
+
+    s/^,/ORDER BY/ for ( $order_by_up, $order_by_down );
+
+    return $order_by_up, $order_by_down;
+  }
 }
+### end-of-FIXME
 
-# !!! THIS IS ALSO HORRIFIC !!! /me ashamed
-#
-# Generates inner/outer select lists for various limit dialects
-# which result in one or more subqueries (e.g. RNO, Top, RowNum)
-# Any non-root-table columns need to have their table qualifier
-# turned into a column alias (otherwise names in subqueries clash
-# and/or lose their source table)
-#
-# Returns inner/outer strings of SQL QUOTED selectors with aliases
-# (to be used in whatever select statement), and an alias index hashref
-# of QUOTED SEL => QUOTED ALIAS pairs (to maybe be used for string-subst
-# higher up).
-# If an order_by is supplied, the inner select needs to bring out columns
-# used in implicit (non-selected) orders, and the order condition itself
-# needs to be realiased to the proper names in the outer query. Thus we
-# also return a hashref (order doesn't matter) of QUOTED EXTRA-SEL =>
-# QUOTED ALIAS pairs, which is a list of extra selectors that do *not*
-# exist in the original select list
+=head1 NAME
 
-sub _subqueried_limit_attrs {
-  my ($self, $rs_attrs) = @_;
+DBIx::Class::SQLMaker::LimitDialects - SQL::Abstract::Limit-like functionality for DBIx::Class::SQLMaker
 
-  croak 'Limit dialect implementation usable only in the context of DBIC (missing $rs_attrs)'
-    unless ref ($rs_attrs) eq 'HASH';
+=head1 DESCRIPTION
 
-  my ($re_sep, $re_alias) = map { quotemeta $_ } (
-    $self->name_sep || '.',
-    $rs_attrs->{alias},
-  );
+This module replicates a lot of the functionality originally found in
+L<SQL::Abstract::Limit>. While simple limits would work as-is, the more
+complex dialects that require e.g. subqueries could not be reliably
+implemented without taking full advantage of the metadata locked within
+L<DBIx::Class::ResultSource> classes. After reimplementation of close to
+80% of the L<SQL::Abstract::Limit> functionality it was deemed more
+practical to simply make an independent DBIx::Class-specific limit-dialect
+provider.
 
-  # correlate select and as, build selection index
-  my (@sel, $in_sel_index);
-  for my $i (0 .. $#{$rs_attrs->{select}}) {
+=head1 SQL LIMIT DIALECTS
 
-    my $s = $rs_attrs->{select}[$i];
-    my $sql_sel = $self->_recurse_fields ($s);
-    my $sql_alias = (ref $s) eq 'HASH' ? $s->{-as} : undef;
+Note that the actual implementations listed below never use C<*> literally.
+Instead proper re-aliasing of selectors and order criteria is done, so that
+the limit dialect are safe to use on joined resultsets with clashing column
+names.
 
+Currently the provided dialects are:
 
-    push @sel, {
-      sql => $sql_sel,
-      unquoted_sql => do { local $self->{quote_char}; $self->_recurse_fields ($s) },
-      as =>
-        $sql_alias
-          ||
-        $rs_attrs->{as}[$i]
-          ||
-        croak "Select argument $i ($s) without corresponding 'as'"
-      ,
-    };
+=cut
 
-    $in_sel_index->{$sql_sel}++;
-    $in_sel_index->{$self->_quote ($sql_alias)}++ if $sql_alias;
+=head2 LimitOffset
 
-    # record unqualified versions too, so we do not have
-    # to reselect the same column twice (in qualified and
-    # unqualified form)
-    if (! ref $s && $sql_sel =~ / $re_sep (.+) $/x) {
-      $in_sel_index->{$1}++;
-    }
-  }
+ SELECT ... LIMIT $limit OFFSET $offset
 
+Supported by B<PostgreSQL> and B<SQLite>
 
-  # re-alias and remove any name separators from aliases,
-  # unless we are dealing with the current source alias
-  # (which will transcend the subqueries as it is necessary
-  # for possible further chaining)
-  my (@in_sel, @out_sel, %renamed);
-  for my $node (@sel) {
-    if (first { $_ =~ / (?<! ^ $re_alias ) $re_sep /x } ($node->{as}, $node->{unquoted_sql}) )  {
-      $node->{as} = $self->_unqualify_colname($node->{as});
-      my $quoted_as = $self->_quote($node->{as});
-      push @in_sel, sprintf '%s AS %s', $node->{sql}, $quoted_as;
-      push @out_sel, $quoted_as;
-      $renamed{$node->{sql}} = $quoted_as;
-    }
-    else {
-      push @in_sel, $node->{sql};
-      push @out_sel, $self->_quote ($node->{as});
-    }
-  }
+=cut
+sub _LimitOffset {
+    my ( $self, $sql, $order, $rows, $offset ) = @_;
+    $sql .= $self->_order_by( $order ) . " LIMIT $rows";
+    $sql .= " OFFSET $offset" if +$offset;
+    return $sql;
+}
 
-  # see if the order gives us anything
-  my %extra_order_sel;
-  for my $chunk ($self->_order_by_chunks ($rs_attrs->{order_by})) {
-    # order with bind
-    $chunk = $chunk->[0] if (ref $chunk) eq 'ARRAY';
-    $chunk =~ s/\s+ (?: ASC|DESC ) \s* $//ix;
+=head2 LimitXY
 
-    next if $in_sel_index->{$chunk};
+ SELECT ... LIMIT $offset $limit
 
-    $extra_order_sel{$chunk} ||= $self->_quote (
-      'ORDER__BY__' . scalar keys %extra_order_sel
-    );
-  }
+Supported by B<MySQL> and any L<SQL::Statement> based DBD
 
-  return (
-    (map { join (', ', @$_ ) } (
-      \@in_sel,
-      \@out_sel)
-    ),
-    \%renamed,
-    keys %extra_order_sel ? \%extra_order_sel : (),
-  );
+=cut
+sub _LimitXY {
+    my ( $self, $sql, $order, $rows, $offset ) = @_;
+    $sql .= $self->_order_by( $order ) . " LIMIT ";
+    $sql .= "$offset, " if +$offset;
+    $sql .= $rows;
+    return $sql;
 }
 
-sub _unqualify_colname {
-  my ($self, $fqcn) = @_;
-  my $re_sep = quotemeta($self->name_sep || '.');
-  $fqcn =~ s/ $re_sep /__/xg;
-  return $fqcn;
-}
+=head2 RowNumberOver
+
+ SELECT * FROM (
+  SELECT *, ROW_NUMBER() OVER( ORDER BY ... ) AS RNO__ROW__INDEX FROM (
+   SELECT ...
+  )
+ ) WHERE RNO__ROW__INDEX BETWEEN ($offset+1) AND ($limit+$offset)
 
-# ANSI standard Limit/Offset implementation. DB2 and MSSQL >= 2005 use this
+
+ANSI standard Limit/Offset implementation. Supported by B<DB2> and
+B<< MSSQL >= 2005 >>.
+
+=cut
 sub _RowNumberOver {
   my ($self, $sql, $rs_attrs, $rows, $offset ) = @_;
 
@@ -231,7 +211,14 @@ sub _rno_default_order {
   return undef;
 }
 
-# Informix specific limit, almost like LIMIT/OFFSET
+=head2 SkipFirst
+
+ SELECT SKIP $offset FIRST $limit * FROM ...
+
+Suported by B<Informix>, almost like LimitOffset. According to
+L<SQL::Abstract::Limit> C<... SKIP $offset LIMIT $limit ...> is also supported.
+
+=cut
 sub _SkipFirst {
   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
 
@@ -249,7 +236,14 @@ sub _SkipFirst {
   );
 }
 
-# Firebird specific limit, reverse of _SkipFirst for Informix
+=head2 FirstSkip
+
+ SELECT FIRST $limit SKIP $offset * FROM ...
+
+Supported by B<Firebird/Interbase>, reverse of SkipFirst. According to
+L<SQL::Abstract::Limit> C<... ROWS $limit TO $offset ...> is also supported.
+
+=cut
 sub _FirstSkip {
   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
 
@@ -267,7 +261,17 @@ sub _FirstSkip {
   );
 }
 
-# WhOracle limits
+=head2 RowNum
+
+ SELECT * FROM (
+  SELECT *, ROWNUM rownum__index FROM (
+   SELECT ...
+  )
+ ) WHERE rownum__index BETWEEN ($offset+1) AND ($limit+$offset)
+
+Supported by B<Oracle>.
+
+=cut
 sub _RowNum {
   my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
 
@@ -295,7 +299,24 @@ EOS
   return $sql;
 }
 
-# Crappy Top based Limit/Offset support. Legacy for MSSQL < 2005
+=head2 Top
+
+ SELECT * FROM
+
+ SELECT TOP $limit FROM (
+  SELECT TOP $limit FROM (
+   SELECT TOP ($limit+$offset) ...
+  ) ORDER BY $reversed_original_order
+ ) ORDER BY $original_order
+
+Unreliable Top-based implementation, supported by B<< MSSQL < 2005 >>.
+
+=head3 CAVEAT
+
+Due to its implementation, this limit dialect returns B<incorrect results>
+when $limit+$offset > total amount of rows in the resultset.
+
+=cut
 sub _Top {
   my ( $self, $sql, $rs_attrs, $rows, $offset ) = @_;
 
@@ -404,8 +425,18 @@ sub _Top {
   return $sql;
 }
 
-# This for Sybase ASE, to use SET ROWCOUNT when there is no offset, and
-# GenericSubQ otherwise.
+=head2 RowCountOrGenericSubQ
+
+This is not exactly a limit dialect, but more of a proxy for B<Sybase ASE>.
+If no $offset is supplied the limit is simply performed as:
+
+ SET ROWCOUNT $limit
+ SELECT ...
+ SET ROWCOUNT 0
+
+Otherwise we fall back to L</GenericSubQ>
+
+=cut
 sub _RowCountOrGenericSubQ {
   my $self = shift;
   my ($sql, $rs_attrs, $rows, $offset) = @_;
@@ -419,11 +450,25 @@ SET ROWCOUNT 0
 EOF
 }
 
-# This is the most evil limit "dialect" (more of a hack) for *really*
-# stupid databases. It works by ordering the set by some unique column,
-# and calculating amount of rows that have a less-er value (thus
-# emulating a RowNum-like index). Of course this implies the set can
-# only be ordered by a single unique columns.
+=head2 GenericSubQ
+
+ SELECT * FROM (
+  SELECT ...
+ )
+ WHERE (
+  SELECT COUNT(*) FROM $original_table cnt WHERE cnt.id < $original_table.id
+ ) BETWEEN $offset AND ($offset+$rows-1)
+
+This is the most evil limit "dialect" (more of a hack) for I<really> stupid
+databases. It works by ordering the set by some unique column, and calculating
+the amount of rows that have a less-er value (thus emulating a L</RowNum>-like
+index). Of course this implies the set can only be ordered by a single unique
+column. Also note that this technique can be and often is B<excruciatingly
+slow>.
+
+Currently used by B<Sybase ASE>, due to lack of any other option.
+
+=cut
 sub _GenericSubQ {
   my ($self, $sql, $rs_attrs, $rows, $offset) = @_;
 
@@ -527,327 +572,127 @@ EOS
 }
 
 
-# While we're at it, this should make LIMIT queries more efficient,
-#  without digging into things too deeply
-sub _find_syntax {
-  my ($self, $syntax) = @_;
-  return $self->{_cached_syntax} ||= $self->SUPER::_find_syntax($syntax);
-}
-
-# Quotes table names, handles "limit" dialects (e.g. where rownum between x and
-# y)
-sub select {
-  my ($self, $table, $fields, $where, $rs_attrs, @rest) = @_;
+# !!! THIS IS ALSO HORRIFIC !!! /me ashamed
+#
+# Generates inner/outer select lists for various limit dialects
+# which result in one or more subqueries (e.g. RNO, Top, RowNum)
+# Any non-root-table columns need to have their table qualifier
+# turned into a column alias (otherwise names in subqueries clash
+# and/or lose their source table)
+#
+# Returns inner/outer strings of SQL QUOTED selectors with aliases
+# (to be used in whatever select statement), and an alias index hashref
+# of QUOTED SEL => QUOTED ALIAS pairs (to maybe be used for string-subst
+# higher up).
+# If an order_by is supplied, the inner select needs to bring out columns
+# used in implicit (non-selected) orders, and the order condition itself
+# needs to be realiased to the proper names in the outer query. Thus we
+# also return a hashref (order doesn't matter) of QUOTED EXTRA-SEL =>
+# QUOTED ALIAS pairs, which is a list of extra selectors that do *not*
+# exist in the original select list
 
-  if (not ref($table) or ref($table) eq 'SCALAR') {
-    $table = $self->_quote($table);
-  }
+sub _subqueried_limit_attrs {
+  my ($self, $rs_attrs) = @_;
 
-  @rest = (-1) unless defined $rest[0];
-  croak "LIMIT 0 Does Not Compute" if $rest[0] == 0;
-    # and anyway, SQL::Abstract::Limit will cause a barf if we don't first
+  croak 'Limit dialect implementation usable only in the context of DBIC (missing $rs_attrs)'
+    unless ref ($rs_attrs) eq 'HASH';
 
-  my ($sql, @bind) = $self->SUPER::select(
-    $table, $self->_recurse_fields($fields), $where, $rs_attrs, @rest
+  my ($re_sep, $re_alias) = map { quotemeta $_ } (
+    $self->name_sep || '.',
+    $rs_attrs->{alias},
   );
-  push @{$self->{where_bind}}, @bind;
 
-# this *must* be called, otherwise extra binds will remain in the sql-maker
-  my @all_bind = $self->_assemble_binds;
+  # correlate select and as, build selection index
+  my (@sel, $in_sel_index);
+  for my $i (0 .. $#{$rs_attrs->{select}}) {
 
-  return wantarray ? ($sql, @all_bind) : $sql;
-}
+    my $s = $rs_attrs->{select}[$i];
+    my $sql_sel = $self->_recurse_fields ($s);
+    my $sql_alias = (ref $s) eq 'HASH' ? $s->{-as} : undef;
 
-sub _assemble_binds {
-  my $self = shift;
-  return map { @{ (delete $self->{"${_}_bind"}) || [] } } (qw/from where having order/);
-}
 
-# Quotes table names, and handles default inserts
-sub insert {
-  my $self = shift;
-  my $table = shift;
-  $table = $self->_quote($table);
+    push @sel, {
+      sql => $sql_sel,
+      unquoted_sql => do { local $self->{quote_char}; $self->_recurse_fields ($s) },
+      as =>
+        $sql_alias
+          ||
+        $rs_attrs->{as}[$i]
+          ||
+        croak "Select argument $i ($s) without corresponding 'as'"
+      ,
+    };
 
-  # SQLA will emit INSERT INTO $table ( ) VALUES ( )
-  # which is sadly understood only by MySQL. Change default behavior here,
-  # until SQLA2 comes with proper dialect support
-  if (! $_[0] or (ref $_[0] eq 'HASH' and !keys %{$_[0]} ) ) {
-    my $sql = "INSERT INTO ${table} DEFAULT VALUES";
+    $in_sel_index->{$sql_sel}++;
+    $in_sel_index->{$self->_quote ($sql_alias)}++ if $sql_alias;
 
-    if (my $ret = ($_[1]||{})->{returning} ) {
-      $sql .= $self->_insert_returning ($ret);
+    # record unqualified versions too, so we do not have
+    # to reselect the same column twice (in qualified and
+    # unqualified form)
+    if (! ref $s && $sql_sel =~ / $re_sep (.+) $/x) {
+      $in_sel_index->{$1}++;
     }
-
-    return $sql;
-  }
-
-  $self->SUPER::insert($table, @_);
-}
-
-# Just quotes table names.
-sub update {
-  my $self = shift;
-  my $table = shift;
-  $table = $self->_quote($table);
-  $self->SUPER::update($table, @_);
-}
-
-# Just quotes table names.
-sub delete {
-  my $self = shift;
-  my $table = shift;
-  $table = $self->_quote($table);
-  $self->SUPER::delete($table, @_);
-}
-
-sub _emulate_limit {
-  my $self = shift;
-  # my ( $syntax, $sql, $order, $rows, $offset ) = @_;
-
-  if ($_[3] == -1) {
-    return $_[1] . $self->_parse_rs_attrs($_[2]);
-  } else {
-    return $self->SUPER::_emulate_limit(@_);
   }
-}
-
-sub _recurse_fields {
-  my ($self, $fields) = @_;
-  my $ref = ref $fields;
-  return $self->_quote($fields) unless $ref;
-  return $$fields if $ref eq 'SCALAR';
-
-  if ($ref eq 'ARRAY') {
-    return join(', ', map { $self->_recurse_fields($_) } @$fields);
-  }
-  elsif ($ref eq 'HASH') {
-    my %hash = %$fields;  # shallow copy
-
-    my $as = delete $hash{-as};   # if supplied
 
-    my ($func, $args, @toomany) = %hash;
 
-    # there should be only one pair
-    if (@toomany) {
-      croak "Malformed select argument - too many keys in hash: " . join (',', keys %$fields );
+  # re-alias and remove any name separators from aliases,
+  # unless we are dealing with the current source alias
+  # (which will transcend the subqueries as it is necessary
+  # for possible further chaining)
+  my (@in_sel, @out_sel, %renamed);
+  for my $node (@sel) {
+    if (first { $_ =~ / (?<! ^ $re_alias ) $re_sep /x } ($node->{as}, $node->{unquoted_sql}) )  {
+      $node->{as} = $self->_unqualify_colname($node->{as});
+      my $quoted_as = $self->_quote($node->{as});
+      push @in_sel, sprintf '%s AS %s', $node->{sql}, $quoted_as;
+      push @out_sel, $quoted_as;
+      $renamed{$node->{sql}} = $quoted_as;
     }
-
-    if (lc ($func) eq 'distinct' && ref $args eq 'ARRAY' && @$args > 1) {
-      croak (
-        'The select => { distinct => ... } syntax is not supported for multiple columns.'
-       .' Instead please use { group_by => [ qw/' . (join ' ', @$args) . '/ ] }'
-       .' or { select => [ qw/' . (join ' ', @$args) . '/ ], distinct => 1 }'
-      );
+    else {
+      push @in_sel, $node->{sql};
+      push @out_sel, $self->_quote ($node->{as});
     }
-
-    my $select = sprintf ('%s( %s )%s',
-      $self->_sqlcase($func),
-      $self->_recurse_fields($args),
-      $as
-        ? sprintf (' %s %s', $self->_sqlcase('as'), $self->_quote ($as) )
-        : ''
-    );
-
-    return $select;
-  }
-  # Is the second check absolutely necessary?
-  elsif ( $ref eq 'REF' and ref($$fields) eq 'ARRAY' ) {
-    return $self->_fold_sqlbind( $fields );
-  }
-  else {
-    croak($ref . qq{ unexpected in _recurse_fields()})
-  }
-}
-
-my $for_syntax = {
-  update => 'FOR UPDATE',
-  shared => 'FOR SHARE',
-};
-
-# this used to be a part of _order_by but is broken out for clarity.
-# What we have been doing forever is hijacking the $order arg of
-# SQLA::select to pass in arbitrary pieces of data (first the group_by,
-# then pretty much the entire resultset attr-hash, as more and more
-# things in the SQLA space need to have mopre info about the $rs they
-# create SQL for. The alternative would be to keep expanding the
-# signature of _select with more and more positional parameters, which
-# is just gross. All hail SQLA2!
-sub _parse_rs_attrs {
-  my ($self, $arg) = @_;
-
-  my $sql = '';
-
-  if (my $g = $self->_recurse_fields($arg->{group_by}) ) {
-    $sql .= $self->_sqlcase(' group by ') . $g;
-  }
-
-  if (defined $arg->{having}) {
-    my ($frag, @bind) = $self->_recurse_where($arg->{having});
-    push(@{$self->{having_bind}}, @bind);
-    $sql .= $self->_sqlcase(' having ') . $frag;
-  }
-
-  if (defined $arg->{order_by}) {
-    $sql .= $self->_order_by ($arg->{order_by});
-  }
-
-  if (my $for = $arg->{for}) {
-    $sql .= " $for_syntax->{$for}" if $for_syntax->{$for};
-  }
-
-  return $sql;
-}
-
-sub _order_by {
-  my ($self, $arg) = @_;
-
-  # check that we are not called in legacy mode (order_by as 4th argument)
-  if (ref $arg eq 'HASH' and not grep { $_ =~ /^-(?:desc|asc)/i } keys %$arg ) {
-    return $self->_parse_rs_attrs ($arg);
-  }
-  else {
-    my ($sql, @bind) = $self->SUPER::_order_by ($arg);
-    push @{$self->{order_bind}}, @bind;
-    return $sql;
   }
-}
-
-sub _order_directions {
-  my ($self, $order) = @_;
-
-  # strip bind values - none of the current _order_directions users support them
-  return $self->SUPER::_order_directions( [ map
-    { ref $_ ? $_->[0] : $_ }
-    $self->_order_by_chunks ($order)
-  ]);
-}
 
-sub _table {
-  my ($self, $from) = @_;
-  if (ref $from eq 'ARRAY') {
-    return $self->_recurse_from(@$from);
-  } elsif (ref $from eq 'HASH') {
-    return $self->_make_as($from);
-  } else {
-    return $from; # would love to quote here but _table ends up getting called
-                  # twice during an ->select without a limit clause due to
-                  # the way S::A::Limit->select works. should maybe consider
-                  # bypassing this and doing S::A::select($self, ...) in
-                  # our select method above. meantime, quoting shims have
-                  # been added to select/insert/update/delete here
-  }
-}
+  # see if the order gives us anything
+  my %extra_order_sel;
+  for my $chunk ($self->_order_by_chunks ($rs_attrs->{order_by})) {
+    # order with bind
+    $chunk = $chunk->[0] if (ref $chunk) eq 'ARRAY';
+    $chunk =~ s/\s+ (?: ASC|DESC ) \s* $//ix;
 
-sub _generate_join_clause {
-    my ($self, $join_type) = @_;
+    next if $in_sel_index->{$chunk};
 
-    return sprintf ('%s JOIN ',
-      $join_type ?  ' ' . uc($join_type) : ''
+    $extra_order_sel{$chunk} ||= $self->_quote (
+      'ORDER__BY__' . scalar keys %extra_order_sel
     );
-}
-
-sub _recurse_from {
-  my ($self, $from, @join) = @_;
-  my @sqlf;
-  push(@sqlf, $self->_make_as($from));
-  foreach my $j (@join) {
-    my ($to, $on) = @$j;
-
-
-    # check whether a join type exists
-    my $to_jt = ref($to) eq 'ARRAY' ? $to->[0] : $to;
-    my $join_type;
-    if (ref($to_jt) eq 'HASH' and defined($to_jt->{-join_type})) {
-      $join_type = $to_jt->{-join_type};
-      $join_type =~ s/^\s+ | \s+$//xg;
-    }
-
-    $join_type = $self->{_default_jointype} if not defined $join_type;
-
-    push @sqlf, $self->_generate_join_clause( $join_type );
-
-    if (ref $to eq 'ARRAY') {
-      push(@sqlf, '(', $self->_recurse_from(@$to), ')');
-    } else {
-      push(@sqlf, $self->_make_as($to));
-    }
-    push(@sqlf, ' ON ', $self->_join_condition($on));
   }
-  return join('', @sqlf);
-}
-
-sub _fold_sqlbind {
-  my ($self, $sqlbind) = @_;
 
-  my @sqlbind = @$$sqlbind; # copy
-  my $sql = shift @sqlbind;
-  push @{$self->{from_bind}}, @sqlbind;
-
-  return $sql;
+  return (
+    (map { join (', ', @$_ ) } (
+      \@in_sel,
+      \@out_sel)
+    ),
+    \%renamed,
+    keys %extra_order_sel ? \%extra_order_sel : (),
+  );
 }
 
-sub _make_as {
-  my ($self, $from) = @_;
-  return join(' ', map { (ref $_ eq 'SCALAR' ? $$_
-                        : ref $_ eq 'REF'    ? $self->_fold_sqlbind($_)
-                        : $self->_quote($_))
-                       } reverse each %{$self->_skip_options($from)});
+sub _unqualify_colname {
+  my ($self, $fqcn) = @_;
+  my $re_sep = quotemeta($self->name_sep || '.');
+  $fqcn =~ s/ $re_sep /__/xg;
+  return $fqcn;
 }
 
-sub _skip_options {
-  my ($self, $hash) = @_;
-  my $clean_hash = {};
-  $clean_hash->{$_} = $hash->{$_}
-    for grep {!/^-/} keys %$hash;
-  return $clean_hash;
-}
+1;
 
-sub _join_condition {
-  my ($self, $cond) = @_;
-  if (ref $cond eq 'HASH') {
-    my %j;
-    for (keys %$cond) {
-      my $v = $cond->{$_};
-      if (ref $v) {
-        croak (ref($v) . qq{ reference arguments are not supported in JOINS - try using \"..." instead'})
-            if ref($v) ne 'SCALAR';
-        $j{$_} = $v;
-      }
-      else {
-        my $x = '= '.$self->_quote($v); $j{$_} = \$x;
-      }
-    };
-    return scalar($self->_recurse_where(\%j));
-  } elsif (ref $cond eq 'ARRAY') {
-    return join(' OR ', map { $self->_join_condition($_) } @$cond);
-  } else {
-    croak "Can't handle this yet!";
-  }
-}
+=head1 AUTHORS
 
-sub limit_dialect {
-    my $self = shift;
-    if (@_) {
-      $self->{limit_dialect} = shift;
-      undef $self->{_cached_syntax};
-    }
-    return $self->{limit_dialect};
-}
+See L<DBIx::Class/CONTRIBUTORS>.
 
-# Set to an array-ref to specify separate left and right quotes for table names.
-# A single scalar is equivalen to [ $char, $char ]
-sub quote_char {
-    my $self = shift;
-    $self->{quote_char} = shift if @_;
-    return $self->{quote_char};
-}
+=head1 LICENSE
 
-# Character separating quoted table names.
-sub name_sep {
-    my $self = shift;
-    $self->{name_sep} = shift if @_;
-    return $self->{name_sep};
-}
+You may distribute this code under the same terms as Perl itself.
 
-1;
+=cut
similarity index 72%
rename from lib/DBIx/Class/SQLAHacks/MSSQL.pm
rename to lib/DBIx/Class/SQLMaker/MSSQL.pm
index f1af970..30b0c96 100644 (file)
@@ -1,7 +1,7 @@
 package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::MSSQL;
+  DBIx::Class::SQLMaker::MSSQL;
 
-use base qw( DBIx::Class::SQLAHacks );
+use base qw( DBIx::Class::SQLMaker );
 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
 
 #
similarity index 90%
rename from lib/DBIx/Class/SQLAHacks/MySQL.pm
rename to lib/DBIx/Class/SQLMaker/MySQL.pm
index cc177f1..16e47e7 100644 (file)
@@ -1,7 +1,7 @@
 package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::MySQL;
+  DBIx::Class::SQLMaker::MySQL;
 
-use base qw( DBIx::Class::SQLAHacks );
+use base qw( DBIx::Class::SQLMaker );
 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
 
 #
similarity index 93%
rename from lib/DBIx/Class/SQLAHacks/Oracle.pm
rename to lib/DBIx/Class/SQLMaker/Oracle.pm
index 23c1285..0a773e7 100644 (file)
@@ -1,10 +1,10 @@
 package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::Oracle;
+  DBIx::Class::SQLMaker::Oracle;
 
 use warnings;
 use strict;
 
-use base qw( DBIx::Class::SQLAHacks );
+use base qw( DBIx::Class::SQLMaker );
 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
 
 sub new {
@@ -184,18 +184,3 @@ sub _unqualify_colname {
 }
 
 1;
-
-__END__
-
-=pod
-
-=head1 NAME
-
-DBIx::Class::SQLAHacks::Oracle - adds hierarchical query support for Oracle to SQL::Abstract
-
-=head1 DESCRIPTION
-
-See L<DBIx::Class::Storage::DBI::Oracle::Generic> for more information about
-how to use hierarchical queries with DBIx::Class.
-
-=cut
similarity index 92%
rename from lib/DBIx/Class/SQLAHacks/OracleJoins.pm
rename to lib/DBIx/Class/SQLMaker/OracleJoins.pm
index 4254aba..8350198 100644 (file)
@@ -1,7 +1,7 @@
 package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::OracleJoins;
+  DBIx::Class::SQLMaker::OracleJoins;
 
-use base qw( DBIx::Class::SQLAHacks );
+use base qw( DBIx::Class::SQLMaker );
 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
 
 sub select {
@@ -90,7 +90,7 @@ sub _recurse_oracle_joins {
 
 =head1 NAME
 
-DBIx::Class::SQLAHacks::OracleJoins - Pre-ANSI Joins-via-Where-Clause Syntax
+DBIx::Class::SQLMaker::OracleJoins - Pre-ANSI Joins-via-Where-Clause Syntax
 
 =head1 PURPOSE
 
@@ -124,7 +124,7 @@ it's already too late.
 
 =item select ($\@$;$$@)
 
-Replaces DBIx::Class::SQLAHacks's select() method, which calls _oracle_joins()
+Replaces DBIx::Class::SQLMaker's select() method, which calls _oracle_joins()
 to modify the column and table list before calling SUPER::select().
 
 =item _recurse_from ($$\@)
@@ -148,7 +148,7 @@ Probably lots more.
 
 =item L<DBIx::Class::Storage::DBI::Oracle::WhereJoins> - Storage class using this
 
-=item L<DBIx::Class::SQLAHacks> - Parent module
+=item L<DBIx::Class::SQLMaker> - Parent module
 
 =item L<DBIx::Class> - Duh
 
similarity index 82%
rename from lib/DBIx/Class/SQLAHacks/SQLite.pm
rename to lib/DBIx/Class/SQLMaker/SQLite.pm
index e260786..24b26ee 100644 (file)
@@ -1,7 +1,7 @@
 package # Hide from PAUSE
-  DBIx::Class::SQLAHacks::SQLite;
+  DBIx::Class::SQLMaker::SQLite;
 
-use base qw( DBIx::Class::SQLAHacks );
+use base qw( DBIx::Class::SQLMaker );
 use Carp::Clan qw/^DBIx::Class|^SQL::Abstract/;
 
 #
index 7b7fa60..9f7ed68 100644 (file)
@@ -336,7 +336,7 @@ sub txn_scope_guard {
 =head2 sql_maker
 
 Returns a C<sql_maker> object - normally an object of class
-C<DBIx::Class::SQLAHacks>.
+C<DBIx::Class::SQLMaker>.
 
 =cut
 
index c26525d..3440ebb 100644 (file)
@@ -7,7 +7,7 @@ use warnings;
 use base qw/DBIx::Class::Storage::DBIHacks DBIx::Class::Storage/;
 use mro 'c3';
 
-use Carp::Clan qw/^DBIx::Class/;
+use Carp::Clan qw/^DBIx::Class|^Try::Tiny/;
 use DBI;
 use DBIx::Class::Storage::DBI::Cursor;
 use DBIx::Class::Storage::Statistics;
@@ -18,12 +18,12 @@ use Try::Tiny;
 use File::Path 'make_path';
 use namespace::clean;
 
+
 # default cursor class, overridable in connect_info attributes
 __PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor');
 
-__PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class/);
-# default
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks');
+__PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class sql_limit_dialect/);
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker');
 
 __PACKAGE__->mk_group_accessors('simple' => qw/
   _connect_info _dbi_connect_info _dbic_connect_attributes _driver_determined
@@ -448,9 +448,9 @@ statement handles via L<DBI/prepare_cached>.
 
 =item limit_dialect
 
-Sets the limit dialect. This is useful for JDBC-bridge among others
-where the remote SQL-dialect cannot be determined by the name of the
-driver alone. See also L<SQL::Abstract::Limit>.
+Sets a specific SQL::Abstract::Limit-style limit dialect, overriding the
+default L</sql_limit_dialect> setting of the storage (if any). For a list
+of available limit dialects see L<DBIx::Class::SQLMaker::LimitDialects>.
 
 =item quote_char
 
@@ -973,23 +973,37 @@ sub _get_dbh {
   return $self->_dbh;
 }
 
-sub _sql_maker_args {
-    my ($self) = @_;
-
-    return (
-      bindtype=>'columns',
-      array_datatypes => 1,
-      limit_dialect => $self->_get_dbh,
-      %{$self->_sql_maker_opts}
-    );
-}
-
 sub sql_maker {
   my ($self) = @_;
   unless ($self->_sql_maker) {
     my $sql_maker_class = $self->sql_maker_class;
     $self->ensure_class_loaded ($sql_maker_class);
-    $self->_sql_maker($sql_maker_class->new( $self->_sql_maker_args ));
+
+    my %opts = %{$self->_sql_maker_opts||{}};
+    my $dialect =
+      $opts{limit_dialect}
+        ||
+      $self->sql_limit_dialect
+        ||
+      do {
+        my $s_class = (ref $self) || $self;
+        carp (
+          "Your storage class ($s_class) does not set sql_limit_dialect and you "
+        . 'have not supplied an explicit limit_dialect in your connection_info. '
+        . 'DBIC will attempt to use the GenericSubQ dialect, which works on most '
+        . 'databases but can be (and often is) painfully slow.'
+        );
+
+        'GenericSubQ';
+      }
+    ;
+
+    $self->_sql_maker($sql_maker_class->new(
+      bindtype=>'columns',
+      array_datatypes => 1,
+      limit_dialect => $dialect,
+      %opts,
+    ));
   }
   return $self->_sql_maker;
 }
@@ -2005,12 +2019,19 @@ sub _select_args {
     }
   }
 
-  # adjust limits
+  # Sanity check the attributes (SQLMaker does it too, but
+  # in case of a software_limit we'll never reach there)
+  if (defined $attrs->{offset}) {
+    $self->throw_exception('A supplied offset attribute must be a non-negative integer')
+      if ( $attrs->{offset} =~ /\D/ or $attrs->{offset} < 0 );
+  }
+  $attrs->{offset} ||= 0;
+
   if (defined $attrs->{rows}) {
-    $self->throw_exception("rows attribute must be positive if present")
-      unless $attrs->{rows} > 0;
+    $self->throw_exception("The rows attribute must be a positive integer if present")
+      if ( $attrs->{rows} =~ /\D/ or $attrs->{rows} <= 0 );
   }
-  elsif (defined $attrs->{offset}) {
+  elsif ($attrs->{offset}) {
     # MySQL actually recommends this approach.  I cringe.
     $attrs->{rows} = $sql_maker->__max_int;
   }
@@ -2111,6 +2132,13 @@ sub select_single {
   return @row;
 }
 
+=head2 sql_limit_dialect
+
+This is an accessor for the default SQL limit dialect used by a particular
+storage driver. Can be overriden by supplying an explicit L</limit_dialect>
+to L<DBIx::Class::Schema/connect>. For a list of available limit dialects
+see L<DBIx::Class::SQLMaker::LimitDialects>.
+
 =head2 sth
 
 =over 4
index 3bad8e0..a5b98c3 100644 (file)
@@ -6,6 +6,8 @@ use warnings;
 use base qw/DBIx::Class::Storage::DBI/;
 use mro 'c3';
 
+__PACKAGE__->sql_limit_dialect ('RowNumberOver');
+
 sub _dbh_last_insert_id {
     my ($self, $dbh, $source, $col) = @_;
 
@@ -19,16 +21,6 @@ sub _dbh_last_insert_id {
 
 sub datetime_parser_type { "DateTime::Format::DB2"; }
 
-sub _sql_maker_opts {
-    my ( $self, $opts ) = @_;
-
-    if ( $opts ) {
-        $self->{_sql_maker_opts} = { %$opts };
-    }
-
-    return { limit_dialect => 'RowNumberOver', %{$self->{_sql_maker_opts}||{}} };
-}
-
 1;
 
 =head1 NAME
index 414c391..4c475d7 100644 (file)
@@ -9,6 +9,8 @@ use Scope::Guard ();
 use Context::Preserve 'preserve_context';
 use namespace::clean;
 
+__PACKAGE__->sql_limit_dialect ('SkipFirst');
+
 __PACKAGE__->mk_group_accessors('simple' => '__last_insert_id');
 
 =head1 NAME
@@ -37,16 +39,6 @@ sub last_insert_id {
   shift->__last_insert_id;
 }
 
-sub _sql_maker_opts {
-  my ( $self, $opts ) = @_;
-
-  if ( $opts ) {
-    $self->{_sql_maker_opts} = { %$opts };
-  }
-
-  return { limit_dialect => 'SkipFirst', %{$self->{_sql_maker_opts}||{}} };
-}
-
 sub _svp_begin {
     my ($self, $name) = @_;
 
index 115a3ee..e14277a 100644 (file)
@@ -15,8 +15,8 @@ DBIx::Class::Storage::DBI::InterBase - Driver for the Firebird RDBMS
 =head1 DESCRIPTION
 
 This class implements autoincrements for Firebird using C<RETURNING> as well as
-L<auto_nextval|DBIx::Class::ResultSource/auto_nextval> sets the limit dialect to
-C<FIRST X SKIP X> and provides L<DBIx::Class::InflateColumn::DateTime> support.
+L<auto_nextval|DBIx::Class::ResultSource/auto_nextval> and provides
+L<DBIx::Class::InflateColumn::DateTime> support.
 
 You need to use either the
 L<disable_sth_caching|DBIx::Class::Storage::DBI/disable_sth_caching> option or
@@ -33,6 +33,7 @@ L</connect_call_datetime_setup>.
 
 # set default
 __PACKAGE__->_use_insert_returning (1);
+__PACKAGE__->sql_limit_dialect ('FirstSkip');
 
 sub _sequence_fetch {
   my ($self, $nextval, $sequence) = @_;
@@ -90,18 +91,6 @@ EOF
   return undef;
 }
 
-# this sub stolen from DB2
-
-sub _sql_maker_opts {
-  my ( $self, $opts ) = @_;
-
-  if ( $opts ) {
-    $self->{_sql_maker_opts} = { %$opts };
-  }
-
-  return { limit_dialect => 'FirstSkip', %{$self->{_sql_maker_opts}||{}} };
-}
-
 sub _svp_begin {
     my ($self, $name) = @_;
 
index 3c6e911..9cde4b9 100644 (file)
@@ -13,7 +13,7 @@ __PACKAGE__->mk_group_accessors(simple => qw/
   _identity _identity_method
 /);
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MSSQL');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::MSSQL');
 
 sub _set_identity_insert {
   my ($self, $table) = @_;
@@ -144,7 +144,7 @@ sub last_insert_id { shift->_identity }
 
 #
 # MSSQL is retarded wrt ordered subselects. One needs to add a TOP
-# to *all* subqueries, but one also can't use TOP 100 PERCENT
+# to *all* subqueries, but one also *can't* use TOP 100 PERCENT
 # http://sqladvice.com/forums/permalink/18496/22931/ShowThread.aspx#22931
 #
 sub _select_args_to_query {
@@ -196,37 +196,26 @@ sub datetime_parser_type {
 
 sub sqlt_type { 'SQLServer' }
 
-sub sql_maker {
+sub sql_limit_dialect {
   my $self = shift;
 
-  unless ($self->_sql_maker) {
-    unless ($self->{_sql_maker_opts}{limit_dialect}) {
-      my $have_rno = 0;
+  my $supports_rno = 0;
 
-      if (exists $self->_server_info->{normalized_dbms_version}) {
-        $have_rno = 1 if $self->_server_info->{normalized_dbms_version} >= 9;
-      }
-      else {
-        # User is connecting via DBD::Sybase and has no permission to run
-        # stored procedures like xp_msver, or version detection failed for some
-        # other reason.
-        # So, we use a query to check if RNO is implemented.
-        try {
-          $self->_get_dbh->selectrow_array('SELECT row_number() OVER (ORDER BY rand())');
-          $have_rno = 1;
-        };
-      }
-
-      $self->{_sql_maker_opts} = {
-        limit_dialect => ($have_rno ? 'RowNumberOver' : 'Top'),
-        %{$self->{_sql_maker_opts}||{}}
-      };
-    }
-
-    my $maker = $self->next::method (@_);
+  if (exists $self->_server_info->{normalized_dbms_version}) {
+    $supports_rno = 1 if $self->_server_info->{normalized_dbms_version} >= 9;
+  }
+  else {
+    # User is connecting via DBD::Sybase and has no permission to run
+    # stored procedures like xp_msver, or version detection failed for some
+    # other reason.
+    # So, we use a query to check if RNO is implemented.
+    try {
+      $self->_get_dbh->selectrow_array('SELECT row_number() OVER (ORDER BY rand())');
+      $supports_rno = 1;
+    };
   }
 
-  return $self->_sql_maker;
+  return $supports_rno ? 'RowNumberOver' : 'Top';
 }
 
 sub _ping {
index 625498a..036c550 100644 (file)
@@ -9,6 +9,8 @@ use DBI;
 
 my $ERR_MSG_START = __PACKAGE__ . ' failed: ';
 
+__PACKAGE__->sql_limit_dialect ('Top');
+
 sub insert {
     my $self = shift;
     my ( $source, $to_insert ) = @_;
index 16be2f8..29e9da9 100644 (file)
@@ -5,6 +5,21 @@ use warnings;
 use base qw/DBIx::Class::Storage::DBI::ODBC/;
 use mro 'c3';
 
+warn 'Major advances took place in the DBIC codebase since this driver'
+  .' (::Storage::DBI::ODBC::DB2_400_SQL) was written. However since the'
+  .' RDBMS in question is so rare it is not possible for us to test any'
+  .' of the "new hottness". If you are using DB2 on AS-400 please get'
+  .' in contact with the developer team:'
+  .' http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class.pm#GETTING_HELP/SUPPORT'
+  ."\n"
+;
+
+# FIXME
+# Most likely all of this code is redundant and unnecessary. We should
+# be able to simply use base qw/DBIx::Class::Storage::DBI::DB2/;
+# Unfortunately nobody has an RDBMS engine to test with, so keeping
+# things as-is for the time being
+
 sub _dbh_last_insert_id {
     my ($self, $dbh, $source, $col) = @_;
 
index 4d8b781..786a000 100644 (file)
@@ -7,6 +7,8 @@ use Context::Preserve 'preserve_context';
 use Try::Tiny;
 use namespace::clean;
 
+__PACKAGE__->sql_limit_dialect ('RowNum');
+
 =head1 NAME
 
 DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class
@@ -77,7 +79,7 @@ versions before 9.
 use base qw/DBIx::Class::Storage::DBI/;
 use mro 'c3';
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::Oracle');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::Oracle');
 
 sub deployment_statements {
   my $self = shift;;
index 945d546..eace5ee 100644 (file)
@@ -6,7 +6,7 @@ use warnings;
 use base qw( DBIx::Class::Storage::DBI::Oracle::Generic );
 use mro 'c3';
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::OracleJoins');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::OracleJoins');
 
 1;
 
@@ -48,7 +48,7 @@ it's already too late.
 
 =head1 METHODS
 
-See L<DBIx::Class::SQLAHacks::OracleJoins> for implementation details.
+See L<DBIx::Class::SQLMaker::OracleJoins> for implementation details.
 
 =head1 BUGS
 
@@ -59,9 +59,9 @@ Probably lots more.
 
 =over
 
-=item L<DBIx::Class::SQLAHacks>
+=item L<DBIx::Class::SQLMaker>
 
-=item L<DBIx::Class::SQLAHacks::OracleJoins>
+=item L<DBIx::Class::SQLMaker::OracleJoins>
 
 =item L<DBIx::Class::Storage::DBI::Oracle::Generic>
 
index ade8c2c..36c9c42 100644 (file)
@@ -13,6 +13,8 @@ use Scope::Guard ();
 use Context::Preserve 'preserve_context';
 use namespace::clean;
 
+__PACKAGE__->sql_limit_dialect ('LimitOffset');
+
 # Ask for a DBD::Pg with array support
 warn __PACKAGE__.": DBD::Pg 2.9.2 or greater is strongly recommended\n"
   if ($DBD::Pg::VERSION < 2.009002);  # pg uses (used?) version::qv()
index 259fc1b..46ad462 100644 (file)
@@ -328,7 +328,6 @@ has 'write_handler' => (
     _dbh
     _select_args
     _dbh_execute_array
-    _sql_maker_args
     _sql_maker
     _query_start
     _sqlt_version_error
@@ -379,6 +378,8 @@ my @unimplemented = qw(
   get_dbms_capability
   set_dbms_capability
 
+  sql_limit_dialect
+
   _dbh_details
 
   _use_insert_returning
index 85b5614..e2efa13 100644 (file)
@@ -8,9 +8,8 @@ use List::Util 'first';
 use Try::Tiny;
 use namespace::clean;
 
-__PACKAGE__->mk_group_accessors(simple => qw/
-  _identity
-/);
+__PACKAGE__->mk_group_accessors(simple => qw/_identity/);
+__PACKAGE__->sql_limit_dialect ('RowNumberOver');
 
 =head1 NAME
 
@@ -18,8 +17,7 @@ DBIx::Class::Storage::DBI::SQLAnywhere - Driver for Sybase SQL Anywhere
 
 =head1 DESCRIPTION
 
-This class implements autoincrements for Sybase SQL Anywhere, selects the
-RowNumberOver limit implementation and provides
+This class implements autoincrements for Sybase SQL Anywhere and provides
 L<DBIx::Class::InflateColumn::DateTime> support.
 
 You need the C<DBD::SQLAnywhere> driver that comes with the SQL Anywhere
@@ -98,18 +96,6 @@ sub _select_args {
   return $self->next::method(@_);
 }
 
-# this sub stolen from DB2
-
-sub _sql_maker_opts {
-  my ( $self, $opts ) = @_;
-
-  if ( $opts ) {
-    $self->{_sql_maker_opts} = { %$opts };
-  }
-
-  return { limit_dialect => 'RowNumberOver', %{$self->{_sql_maker_opts}||{}} };
-}
-
 # this sub stolen from MSSQL
 
 sub build_datetime_parser {
index bffd133..766eb13 100644 (file)
@@ -10,7 +10,8 @@ use POSIX 'strftime';
 use File::Copy;
 use File::Spec;
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::SQLite');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::SQLite');
+__PACKAGE__->sql_limit_dialect ('LimitOffset');
 
 sub backup
 {
index 637d4ec..0496634 100644 (file)
@@ -16,6 +16,8 @@ use Data::Dumper::Concise 'Dumper';
 use Try::Tiny;
 use namespace::clean;
 
+__PACKAGE__->sql_limit_dialect ('RowCountOrGenericSubQ');
+
 __PACKAGE__->mk_group_accessors('simple' =>
     qw/_identity _blob_log_on_update _writer_storage _is_extra_storage
        _bulk_storage _is_bulk_storage _began_bulk_work
@@ -152,16 +154,6 @@ for my $method (@also_proxy_to_extra_storages) {
   };
 }
 
-sub _sql_maker_opts {
-  my ( $self, $opts ) = @_;
-
-  if ( $opts ) {
-    $self->{_sql_maker_opts} = { %$opts };
-  }
-
-  return { limit_dialect => 'RowCountOrGenericSubQ', %{$self->{_sql_maker_opts}||{}} };
-}
-
 sub disconnect {
   my $self = shift;
 
@@ -1160,7 +1152,7 @@ Real limits and limited counts using stored procedures deployed on startup.
 
 =item *
 
-Adaptive Server Anywhere (ASA) support, with possible SQLA::Limit support.
+Adaptive Server Anywhere (ASA) support
 
 =item *
 
index 7d80187..2c49691 100644 (file)
@@ -9,7 +9,8 @@ use base qw/
 /;
 use mro 'c3';
 
-__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MySQL');
+__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker::MySQL');
+__PACKAGE__->sql_limit_dialect ('LimitXY');
 
 sub with_deferred_fk_checks {
   my ($self, $sub) = @_;
index 8d5a323..0526dd8 100644 (file)
@@ -332,6 +332,6 @@ ZEROINSEARCH: {
 
 my $schema2 = DBICTest::Schema->connect($dsn, $user, $pass);
 $schema2->resultset("Artist")->find(4);
-isa_ok($schema2->storage->sql_maker, 'DBIx::Class::SQLAHacks::MySQL');
+isa_ok($schema2->storage->sql_maker, 'DBIx::Class::SQLMaker::MySQL');
 
 done_testing;
index 162c276..a981fc8 100644 (file)
--- a/t/72pg.t
+++ b/t/72pg.t
@@ -53,6 +53,27 @@ DBICTest::Schema->load_classes( map {s/.+:://;$_} @test_classes ) if @test_class
     ok (!$s->storage->_dbh, 'still not connected');
   }
 
+# test LIMIT support
+{
+  my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
+  drop_test_schema($schema);
+  create_test_schema($schema);
+  for (1..6) {
+    $schema->resultset('Artist')->create({ name => 'Artist ' . $_ });
+  }
+  my $it = $schema->resultset('Artist')->search( {},
+    { rows => 3,
+      offset => 2,
+      order_by => 'artistid' }
+  );
+  is( $it->count, 3, "LIMIT count ok" );  # ask for 3 rows out of 6 artists
+  is( $it->next->name, "Artist 3", "iterator->next ok" );
+  $it->next;
+  $it->next;
+  $it->next;
+  is( $it->next, undef, "next past end of resultset ok" );
+}
+
 # check if we indeed do support stuff
 my $test_server_supports_insert_returning = do {
   my $v = DBICTest::Schema->connect($dsn, $user, $pass)
index 3397f3c..99eb601 100644 (file)
@@ -173,15 +173,10 @@ SQL
 
     my $have_rno = $version >= 9 ? 1 : 0;
 
-    local $storage->{_sql_maker}        = undef;
-    local $storage->{_sql_maker_opts}   = undef;
-
     local $storage->{_dbh_details}{info} = {}; # delete cache
 
-    $storage->sql_maker;
-
     my $rno_detected =
-      ($storage->{_sql_maker_opts}{limit_dialect} eq 'RowNumberOver') ? 1 : 0;
+      ($storage->sql_limit_dialect eq 'RowNumberOver') ? 1 : 0;
 
     ok (($have_rno == $rno_detected),
       'row_number() over support detected correctly');
index 862fef3..88749b4 100644 (file)
@@ -11,7 +11,7 @@ my $schema = DBICTest->init_schema();
 my $orig_debug = $schema->storage->debug;
 
 # test the abstract join => SQL generator
-my $sa = new DBIx::Class::SQLAHacks;
+my $sa = new DBIx::Class::SQLMaker;
 
 my @j = (
     { child => 'person' },
similarity index 96%
rename from t/sqlahacks/sql_maker/sql_maker.t
rename to t/sqlmaker/core.t
index ec137e1..2cf88ba 100644 (file)
@@ -69,7 +69,7 @@ my $sql_maker = $schema->storage->sql_maker;
   );
 }
 
-# Make sure the carp/croak override in SQLA works (via SQLAHacks)
+# Make sure the carp/croak override in SQLA works (via SQLMaker)
 my $file = quotemeta (__FILE__);
 throws_ok (sub {
   $schema->resultset ('Artist')->search ({}, { order_by => { -asc => 'stuff', -desc => 'staff' } } )->as_query;
diff --git a/t/sqlmaker/limit_dialects/custom.t b/t/sqlmaker/limit_dialects/custom.t
new file mode 100644 (file)
index 0000000..4a78951
--- /dev/null
@@ -0,0 +1,50 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use lib qw(t/lib);
+use DBICTest::Schema;
+use DBIC::SqlMakerTest;
+
+# This is legacy stuff from SQL::Absract::Limit
+# Keep it around just in case someone is using it
+
+{
+  package DBICTest::SQLMaker::CustomDialect;
+  use base qw/DBIx::Class::SQLMaker/;
+  sub emulate_limit {
+    my ($self, $sql, $rs_attrs, $limit, $offset) = @_;
+    return sprintf ('shiny sproc ((%s), %d, %d)',
+      $sql,
+      $limit || 0,
+      $offset || 0,
+    );
+  }
+}
+
+my $s = DBICTest::Schema->connect ('dbi:SQLite::memory:');
+$s->storage->sql_maker_class ('DBICTest::SQLMaker::CustomDialect');
+
+my $rs = $s->resultset ('CD');
+is_same_sql_bind (
+  $rs->search ({}, { rows => 1, offset => 3,columns => [
+      { id => 'foo.id' },
+      { 'bar.id' => 'bar.id' },
+      { bleh => \ 'TO_CHAR (foo.womble, "blah")' },
+    ]})->as_query,
+  '(
+    shiny sproc (
+      (
+        SELECT foo.id, bar.id, TO_CHAR (foo.womble, "blah")
+          FROM cd me
+      ),
+      1,
+      3
+    )
+  )',
+  [],
+  'Rownum subsel aliasing works correctly'
+);
+
+done_testing;
similarity index 94%
rename from t/sqlahacks/oracle.t
rename to t/sqlmaker/oracle.t
index 67a908e..6b9e6f9 100644 (file)
@@ -6,7 +6,7 @@ use Test::Exception;
 use Data::Dumper::Concise;
 use lib qw(t/lib);
 use DBIC::SqlMakerTest;
-use DBIx::Class::SQLAHacks::Oracle;
+use DBIx::Class::SQLMaker::Oracle;
 
 # 
 #  Offline test for connect_by 
@@ -52,8 +52,8 @@ my @handle_tests = (
     # TODO: NOCYCLE parameter doesn't work
 );
 
-my $sqla_oracle = DBIx::Class::SQLAHacks::Oracle->new( quote_char => '"', name_sep => '.' );
-isa_ok($sqla_oracle, 'DBIx::Class::SQLAHacks::Oracle');
+my $sqla_oracle = DBIx::Class::SQLMaker::Oracle->new( quote_char => '"', name_sep => '.' );
+isa_ok($sqla_oracle, 'DBIx::Class::SQLMaker::Oracle');
 
 
 for my $case (@handle_tests) {
similarity index 95%
rename from t/sqlahacks/oraclejoin.t
rename to t/sqlmaker/oraclejoin.t
index 83c8332..53ba94c 100644 (file)
@@ -4,11 +4,11 @@ use warnings;
 use Test::More;
 
 use lib qw(t/lib);
-use DBIx::Class::SQLAHacks::OracleJoins;
+use DBIx::Class::SQLMaker::OracleJoins;
 use DBICTest;
 use DBIC::SqlMakerTest;
 
-my $sa = new DBIx::Class::SQLAHacks::OracleJoins;
+my $sa = new DBIx::Class::SQLMaker::OracleJoins;
 
 # search with undefined or empty $cond
 
similarity index 100%
rename from t/sqlahacks/sqlite.t
rename to t/sqlmaker/sqlite.t
index 18df9cf..7f45665 100644 (file)
@@ -113,9 +113,10 @@ my $exceptions = {
 
 # test some specific components whose parents are exempt below
     'DBIx::Class::Relationship::Base'               => {},
+    'DBIx::Class::SQLMaker::LimitDialects'          => {},
 
 # internals
-    'DBIx::Class::SQLAHacks*'                       => { skip => 1 },
+    'DBIx::Class::SQLMaker*'                        => { skip => 1 },
     'DBIx::Class::Storage::DBI*'                    => { skip => 1 },
     'SQL::Translator::*'                            => { skip => 1 },