| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package RapidApp::TableSpec::ColSpec; | 
| 2 | 5 |  |  | 5 |  | 30 | use strict; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 150 |  | 
| 3 | 5 |  |  | 5 |  | 26 | use Moose; | 
|  | 5 |  |  |  |  | 11 |  | 
|  | 5 |  |  |  |  | 26 |  | 
| 4 | 5 |  |  | 5 |  | 26689 | use Moose::Util::TypeConstraints; | 
|  | 5 |  |  |  |  | 13 |  | 
|  | 5 |  |  |  |  | 33 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 5 |  |  | 5 |  | 8930 | use RapidApp::Util qw(:all); | 
|  | 5 |  |  |  |  | 13 |  | 
|  | 5 |  |  |  |  | 2168 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | =head1 ColSpec format 'include_colspec' | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | The include_colspec attribute defines joins and columns to include. It consists | 
| 11 |  |  |  |  |  |  | of a list of "ColSpecs" | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | The ColSpec format is a string format consisting of consists of 2 parts: an | 
| 14 |  |  |  |  |  |  | optional 'relspec' followed by a 'colspec'. The last dot "." in the string separates | 
| 15 |  |  |  |  |  |  | the relspec on the left from the colspec on the right. A string without periods | 
| 16 |  |  |  |  |  |  | has no (or an empty '') relspec. | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | The relspec is a chain of relationship names delimited by dots. These must be exact | 
| 19 |  |  |  |  |  |  | relnames in the correct order. These are used to create the base DBIC join attr. For | 
| 20 |  |  |  |  |  |  | example, this relspec (to the left of .*): | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | object.owner.contact.* | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | Would become this join: | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | { object => { owner => 'contact' } } | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | Multple overlapping rels are collapsed in an inteligent manner. For example, this: | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | object.owner.contact.* | 
| 31 |  |  |  |  |  |  | object.owner.notes.* | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | Gets collapsed into this join: | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | { object => { owner => [ 'contact', 'notes' ] } } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | The colspec to the right of the last dot "." is a glob pattern match string to identify | 
| 38 |  |  |  |  |  |  | which columns of that last relationship to include. Standard simple glob wildcards * ? [ ] | 
| 39 |  |  |  |  |  |  | are supported (this is powered by the Text::Glob module. ColSpecs with no relspec apply to | 
| 40 |  |  |  |  |  |  | the base table/class. If no base colspecs are defined, '*' is assumed, which will include | 
| 41 |  |  |  |  |  |  | all columns of the base table (but not of any related tables). | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | Note that this ColSpec: | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | object.owner.contact | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | Would join { object => 'owner' } and include one column named 'contact' within the owner table. | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | This ColSpec, on the other hand: | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | object.owner.contact.* | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | Would join { object => { owner => 'contact' } } and include all columns within the contact table. | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | The ! chacter can exclude instead of include. It can only be at the start of the line, and it will | 
| 56 |  |  |  |  |  |  | cause the colspec to exclude columns that match the pattern. For the purposes of joining, ! ColSpecs | 
| 57 |  |  |  |  |  |  | are ignored. | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | =head1 EXAMPLE ColSpecs: | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | 'name', | 
| 62 |  |  |  |  |  |  | '!id', | 
| 63 |  |  |  |  |  |  | '*', | 
| 64 |  |  |  |  |  |  | '!*', | 
| 65 |  |  |  |  |  |  | 'project.*', | 
| 66 |  |  |  |  |  |  | 'user.*', | 
| 67 |  |  |  |  |  |  | 'contact.notes.owner.foo*', | 
| 68 |  |  |  |  |  |  | 'contact.notes.owner.foo.sd', | 
| 69 |  |  |  |  |  |  | 'project.dist1.rsm.object.*_ts', | 
| 70 |  |  |  |  |  |  | 'relation.column', | 
| 71 |  |  |  |  |  |  | 'owner.*', | 
| 72 |  |  |  |  |  |  | '!owner.*_*', | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =cut | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  |  | 
| 77 | 5 |  |  | 5 |  | 1098 | use Type::Tiny; | 
|  | 5 |  |  |  |  | 25419 |  | 
|  | 5 |  |  |  |  | 5716 |  | 
| 78 |  |  |  |  |  |  | my $TYPE_ColSpecStr = Type::Tiny->new( | 
| 79 |  |  |  |  |  |  | name       => "ColSpecStr", | 
| 80 |  |  |  |  |  |  | constraint => sub { | 
| 81 |  |  |  |  |  |  | /\s+/ and warn "ColSpec '$_' is invalid because it contains whitespace" and return 0; | 
| 82 |  |  |  |  |  |  | #/[A-Z]+/ and warn "ColSpec '$_' is invalid because it contains upper case characters" and return 0; | 
| 83 |  |  |  |  |  |  | /([^\#a-zA-Z0-9\-\_\.\!\*\?\[\]\{\}\:])/ and warn "ColSpec '$_' contains invalid characters ('$1')." and return 0; | 
| 84 |  |  |  |  |  |  | /^\./ and warn "ColSpec '$_' is invalid: \".\" cannot be the first character" and return 0; | 
| 85 |  |  |  |  |  |  | /\.$/ and warn "ColSpec '$_' is invalid: \".\" cannot be the last character (did you mean '$_*' ?)" and return 0; | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | $_ =~ s/^\#//; | 
| 88 |  |  |  |  |  |  | /\#/ and warn "ColSpec '$_' is invalid: # (comment) character may only be supplied at the begining of the string." and return 0; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | $_ =~ s/^\!//; | 
| 91 |  |  |  |  |  |  | /\!/ and warn "ColSpec '$_' is invalid: ! (not) character may only be supplied at the begining of the string." and return 0; | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | return 1; | 
| 94 |  |  |  |  |  |  | }, | 
| 95 |  |  |  |  |  |  | message => sub { "$_ not a ColSpecStr (see previous warnings)" } | 
| 96 |  |  |  |  |  |  | ); | 
| 97 | 0 |  |  | 0 | 0 | 0 | sub ColSpecStr { $TYPE_ColSpecStr } | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | subtype 'ColSpecStr', as 'Str', where { $TYPE_ColSpecStr->constraint->(@_) }; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | has 'colspecs', is => 'ro', isa => 'ArrayRef[ColSpecStr]', required => 1; | 
| 102 | 2986 |  |  | 2986 | 0 | 3472 | sub all_colspecs { uniq( @{(shift)->colspecs} ) } | 
|  | 2986 |  |  |  |  | 66838 |  | 
| 103 |  |  |  |  |  |  | sub add_colspecs { push @{(shift)->colspecs}, @_ } | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | # Store the orig/init colspec data in 'init_colspecs' | 
| 107 |  |  |  |  |  |  | has 'init_colspecs', is => 'ro', required => 1; | 
| 108 |  |  |  |  |  |  | around BUILDARGS => sub { | 
| 109 |  |  |  |  |  |  | my $orig = shift; | 
| 110 |  |  |  |  |  |  | my $class = shift; | 
| 111 |  |  |  |  |  |  | my %params = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref | 
| 112 |  |  |  |  |  |  | $params{init_colspecs} = [ @{$params{colspecs}} ] if (ref($params{colspecs}) eq 'ARRAY'); | 
| 113 |  |  |  |  |  |  | return $class->$orig(%params); | 
| 114 |  |  |  |  |  |  | }; | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | sub BUILD { | 
| 117 | 1338 |  |  | 1338 | 0 | 984772 | my $self = shift; | 
| 118 | 1338 |  |  |  |  | 3182 | $self->regen_subspec; | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | after 'expand_colspecs' => sub { (shift)->regen_subspec(@_) }; | 
| 122 |  |  |  |  |  |  | after 'add_colspecs' => sub { (shift)->regen_subspec(@_) }; | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | sub expand_colspecs { | 
| 126 |  |  |  |  |  |  | my $self = shift; | 
| 127 |  |  |  |  |  |  | my $code = shift; | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | @{$self->colspecs} = $code->(@{$self->colspecs}); | 
| 130 |  |  |  |  |  |  | } | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub regen_subspec { | 
| 135 | 2426 |  |  | 2426 | 0 | 3422 | my $self = shift; | 
| 136 | 2426 |  |  |  |  | 71035 | $self->_clear_rel_order; | 
| 137 | 2426 |  |  |  |  | 62567 | $self->_clear_subspec; | 
| 138 | 2426 |  |  |  |  | 66474 | $self->_clear_subspec_data; | 
| 139 | 2426 |  |  |  |  | 52990 | $self->subspec; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | has 'rel_order', is => 'ro', lazy => 1, clearer => '_clear_rel_order', default => sub { | 
| 144 |  |  |  |  |  |  | my $self = shift; | 
| 145 |  |  |  |  |  |  | return $self->_subspec_data->{order}; | 
| 146 |  |  |  |  |  |  | }, isa => 'ArrayRef'; | 
| 147 | 2602 |  |  | 2602 | 0 | 3400 | sub all_rel_order   { uniq( @{(shift)->rel_order} ) } | 
|  | 2602 |  |  |  |  | 58203 |  | 
| 148 | 2426 |  |  | 2426 | 0 | 4152 | sub count_rel_order { scalar( (shift)->all_rel_order ) } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | has 'subspec', is => 'ro', lazy => 1, clearer => '_clear_subspec', default => sub { | 
| 151 |  |  |  |  |  |  | my $self = shift; | 
| 152 |  |  |  |  |  |  | my $data = $self->_subspec_data->{data}; | 
| 153 |  |  |  |  |  |  | return { '' => $self } unless ($self->count_rel_order > 1); | 
| 154 |  |  |  |  |  |  | return { map { $_ => __PACKAGE__->new(colspecs => $data->{$_}) } keys %$data }; | 
| 155 |  |  |  |  |  |  | }, isa => 'HashRef'; | 
| 156 | 688 |  |  | 688 | 0 | 15119 | sub get_subspec { (shift)->subspec->{$_[0]} } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | has '_subspec_data', is => 'ro', isa => 'HashRef', lazy => 1,  clearer => '_clear_subspec_data', | 
| 161 |  |  |  |  |  |  | default => sub { | 
| 162 |  |  |  |  |  |  | my $self = shift; | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | my @order = (''); | 
| 165 |  |  |  |  |  |  | my %data = ('' => []); | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | my %end_rels = ( '' => 1 ); | 
| 168 |  |  |  |  |  |  | foreach my $spec ($self->all_colspecs) { | 
| 169 |  |  |  |  |  |  | my $pre; { my ($match) = ($spec =~ /^(\!)/); $spec =~ s/^(\!)//; $pre = $match ? $match : ''; } | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | my @parts = split(/\./,$spec); | 
| 172 |  |  |  |  |  |  | my $rel = shift @parts; | 
| 173 |  |  |  |  |  |  | my $subspec = join('.',@parts); | 
| 174 |  |  |  |  |  |  | unless(@parts > 0) { # <-- if its the base rel | 
| 175 |  |  |  |  |  |  | $subspec = $rel; | 
| 176 |  |  |  |  |  |  | $rel = ''; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | # end rels that link to colspecs and not just to relspecs | 
| 180 |  |  |  |  |  |  | # (intermediate rels with no direct columns) | 
| 181 |  |  |  |  |  |  | $end_rels{$rel}++ if ( | 
| 182 |  |  |  |  |  |  | not $subspec =~ /\./ and | 
| 183 |  |  |  |  |  |  | $pre eq '' | 
| 184 |  |  |  |  |  |  | ); | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | unless(defined $data{$rel}) { | 
| 187 |  |  |  |  |  |  | $data{$rel} = []; | 
| 188 |  |  |  |  |  |  | push @order, $rel; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | push @{$data{$rel}}, $pre . $subspec; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | # Set the base colspec to '*' if its empty: | 
| 195 |  |  |  |  |  |  | push @{$data{''}}, '*' unless (@{$data{''}} > 0); | 
| 196 |  |  |  |  |  |  | $end_rels{$_} or push @{$data{$_}}, '!*' for (@order); | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | return { | 
| 199 |  |  |  |  |  |  | data => \%data, | 
| 200 |  |  |  |  |  |  | order => \@order | 
| 201 |  |  |  |  |  |  | }; | 
| 202 |  |  |  |  |  |  | }; | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | sub base_colspec { | 
| 205 | 352 |  |  | 352 | 0 | 640 | my $self = shift; | 
| 206 | 352 |  |  |  |  | 892 | return $self->get_subspec(''); | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | 1; |