SELECT statement generation
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / lib / DBIx / Class / ResultSource / MultipleTableInheritance.pm
index a25090e..8f5fe21 100644 (file)
@@ -1,4 +1,13 @@
-use MooseX::Declare;
+package DBIx::Class::ResultSource::MultipleTableInheritance;
+
+use strict;
+use warnings;
+use parent qw(DBIx::Class::ResultSource::View);
+use Method::Signatures::Simple;
+use Carp::Clan qw/^DBIx::Class/;
+use aliased 'DBIx::Class::ResultSource::Table';
+use aliased 'DBIx::Class::ResultClass::HashRefInflator';
+use namespace::autoclean;
 
 # how this works:
 #
@@ -15,11 +24,162 @@ use MooseX::Declare;
 #
 # deploying the postgres rules through SQLT may be a pain though.
 
-class DBIx::Class::ResultSource::MultipleTableInheritance
-  extends DBIx::Class::ResultSource::View {
+__PACKAGE__->mk_group_accessors(simple => qw(parent_source));
+
+method new ($class: @args) {
+  my $new = $class->next::method(@args);
+  my $rc = $new->result_class;
+  if (my $meth = $rc->can('result_source_instance')) {
+    my $source = $rc->$meth;
+    if ($source->result_class ne $new->result_class
+        && $new->result_class->isa($source->result_class)) {
+      $new->parent_source($source);
+    }
+  }
+  return $new;
+}
+
+method schema (@args) {
+  my $ret = $self->next::method(@args);
+  if (@args) {
+    $self->_attach_additional_sources;
+  }
+  return $ret;
+}
+
+method _attach_additional_sources () {
+  my $raw_name = $self->raw_source_name;
+  my $schema = $self->schema;
+
+  # if the raw source is already present we can assume we're done
+  return if grep { $_ eq $raw_name } $schema->sources;
+
+  # our parent should've been registered already actually due to DBIC
+  # attaching subclass sources later in load_namespaces
+
+  my $parent;
+  if ($self->parent_source) {
+      my $parent_name = $self->parent_source->name;
+    ($parent) = 
+      grep { $_->name eq $parent_name }
+        map $schema->source($_), $schema->sources;
+    confess "Couldn't find attached source for parent $parent_name - did you use load_classes? This module is only compatible with load_namespaces"
+      unless $parent;
+  }
+
+  # create the raw table source
+
+  my $table = Table->new({ name => $self->raw_table_name });
+
+  # we don't need to add the PK cols explicitly if we're the root table
+  # since they'll get added below
+
+  if ($parent) {
+    my %join;
+    foreach my $pri ($self->primary_columns) {
+      my %info = %{$self->column_info($pri)};
+      delete @info{qw(is_auto_increment sequence auto_nextval)};
+      $table->add_column($pri => \%info);
+      $join{"foreign.${pri}"} = "self.${pri}";
+    }
+    # have to use source name lookups rather than result class here
+    # because we don't actually have a result class on the raw sources
+    $table->add_relationship('parent', $parent->raw_source_name, \%join);
+  }
+
+  # add every column that's actually a concrete part of us
+
+  $table->add_columns(
+    map { ($_ => { %{$self->column_info($_)} }) }
+      grep { $self->column_info($_)->{originally_defined_in} eq $self->name }
+        $self->columns
+  );
+  $table->set_primary_key($self->primary_columns);
+  $schema->register_source($raw_name => $table);
+}
+
+method set_primary_key (@args) {
+  if ($self->parent_source) {
+    confess "Can't set primary key on a subclass";
+  }
+  return $self->next::method(@args);
+}
+
+method raw_source_name () {
+  my $base = $self->source_name;
+  confess "Can't generate raw source name when we don't have a source_name"
+    unless $base;
+  return 'Raw::'.$base;
+}
+
+method raw_table_name () {
+  return '_'.$self->name;
+}
+
+method add_columns (@args) {
+  my $ret = $self->next::method(@args);
+  $_->{originally_defined_in} ||= $self->name for values %{$self->_columns};
+  return $ret;
+}
+
+BEGIN {
+
+  # helper routines, constructed as anon subs so autoclean nukes them
+
+  use signatures;
+
+  *argify = sub (@names) {
+    map '_'.$_, @names;
+  };
+
+  *qualify_with = sub ($source, @names) {
+    map join('.', $source->name, $_), @names;
+  };
+
+  *body_cols = sub ($source) {
+    my %pk; @pk{$source->primary_columns} = ();
+    map +{ %{$source->column_info($_)}, name => $_ },
+      grep !exists $pk{$_}, $source->columns;
+  };
+
+  *pk_cols = sub ($source) {
+    map +{ %{$source->column_info($_)}, name => $_ },
+      $source->primary_columns;
+  };
+
+  *names_of = sub (@cols) { map $_->{name}, @cols };
+
+  *arglist = sub (@cols) {
+    map join(' ', @{$_}{qw(name data_type)}), @cols;
+  };
+    
+}
 
-  
-  
+method view_definition () {
+  my $schema = $self->schema;
+  confess "Can't generate view without connected schema, sorry"
+    unless $schema && $schema->storage;
+  my $sqla = $schema->storage->sql_maker;
+  my @sources = my $table = $self->schema->source($self->raw_source_name);
+  my $super_view = $self->parent_source;
+  push(@sources, $super_view) if defined($super_view);
+  my @body_cols = map body_cols($_), @sources;
+  my @pk_cols = pk_cols $self;
+  my $select = $sqla->select(
+    ($super_view
+      ? ([   # FROM _tbl _tbl
+           { $table->name => $table->name },
+           [ # JOIN view view
+             { $super_view->name => $super_view->name },
+             # ON _tbl.id = view.id
+             { map +(qualify_with($super_view, $_), qualify_with($table, $_)),
+                 names_of @pk_cols }
+           ]
+         ])
+      : ($table->name)),
+    [ (qualify_with $table, names_of @pk_cols), names_of @body_cols ],
+  );
+  return $select;
 }
 
 1;