The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Rose::DB::Object::Metadata::Auto::Pg;

use strict;

use Carp();

use Rose::DB::Object::Metadata::UniqueKey;

use Rose::DB::Object::Metadata::Auto;
our @ISA = qw(Rose::DB::Object::Metadata::Auto);

our $Debug;

our $VERSION = '0.784';

# Other useful columns, not selected for now
#   pg_get_indexdef(i.oid) AS indexdef
#   n.nspname AS schemaname, 
#   c.relname AS tablename,
#   i.relname AS indexname,
#   t.spcname AS "tablespace",
#   x.indisunique AS is_unique_index,
#
# Plus this join condition for table "t"
# LEFT JOIN pg_catalog.pg_tablespace t ON t.oid = i.reltablespace
use constant UNIQUE_INDEX_SQL => <<'EOF';
SELECT 
  x.indrelid,
  x.indkey,
  i.relname AS key_name,
  CASE WHEN x.indpred IS NULL THEN 0 ELSE 1 END AS has_predicate
FROM 
  pg_catalog.pg_index x
  JOIN pg_catalog.pg_class c ON c.oid = x.indrelid
  JOIN pg_catalog.pg_class i ON i.oid = x.indexrelid
  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE
  x.indisunique = 't' AND
  c.relkind     = 'r' AND 
  i.relkind     = 'i' AND
  n.nspname     = ?   AND
  c.relname     = ?
EOF

use constant UNIQUE_INDEX_COLUMNS_SQL_STUB => <<'EOF';
SELECT
  attname
FROM
  pg_catalog.pg_attribute
WHERE
  attrelid = ? AND
  attnum 
EOF

sub auto_generate_unique_keys
{
  my($self) = shift;

  unless(defined wantarray)
  {
    Carp::croak "Useless call to auto_generate_unique_keys() in void context";
  }

  my($class, @unique_keys, $error);

  TRY:
  {
    local $@;

    eval
    {
      $class = $self->class or die "Missing class!";

      my $db  = $self->db;
      my $dbh = $db->dbh or die $db->error;

      local $dbh->{'FetchHashKeyName'} = 'NAME';

      my $schema = $self->select_schema($db);
      $schema = $db->default_implicit_schema  unless(defined $schema);
      $schema = lc $schema  if(defined $schema);

      my $table = lc $self->table;

      my($relation_id, $column_nums, $key_name, $has_predicate);

      my $sth = $dbh->prepare(UNIQUE_INDEX_SQL);

      $sth->execute($schema, $table);
      $sth->bind_columns(\($relation_id, $column_nums, $key_name, $has_predicate));

      while($sth->fetch)
      {
        # See if we need to ignore predicated unique indices.  The semantics 
        # of predicated indexes, e.g.,
        #
        #    CREATE UNIQUE INDEX ... WHERE column = 'value'
        #
        # are different from RDBO's unique key semantics in that predicates
        # (may) cause the index to apply only partially to the table.
        if($has_predicate && !$self->include_predicated_unique_indexes)
        {
          $Debug && warn "$class - Skipping predicated unique index $key_name\n";
          next;
        }

        # Skip functional indexes (e.g., "... ON (LOWER(name))") which show up
        # as having a pg_index.indkey ($column_nums) value of 0.
        next  if($column_nums eq '0'); 

        my $uk = 
          Rose::DB::Object::Metadata::UniqueKey->new(
            name          => $key_name,
            parent        => $self,
            has_predicate => $has_predicate);

        # column_nums is a space-separated list of numbers.  It's really an
        # "in2vector" data type, which seems sketchy to me, but whatever. 
        # We can fall back to the pg_get_indexdef() function and try to
        # parse that mess if this ever stops working.
        my @column_nums = grep { /^\d+$/ } split(/\s+/, $column_nums);

        my $col_sth = $dbh->prepare(UNIQUE_INDEX_COLUMNS_SQL_STUB . 
                                   ' IN(' . join(', ', @column_nums) . ')');

        my($column, @columns);

        $col_sth->execute($relation_id);
        $col_sth->bind_columns(\$column);

        while($col_sth->fetch)
        {
          push(@columns, $column);
        }

        unless(@columns)
        {
          die "No columns found for relation id $relation_id, column numbers @column_nums";
        }

        $uk->columns(\@columns);

        push(@unique_keys, $uk);
      }
    };

    $error = $@;
  }

  if($error)
  {
    Carp::croak "Could not auto-retrieve unique keys for class $class - $error";
  }

  # This sort order is part of the API, and is essential to make the
  # test suite work.
  @unique_keys = sort { lc $a->name cmp lc $b->name } @unique_keys;

  return wantarray ? @unique_keys : \@unique_keys;
}

1;