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; |