line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RapidApp::Module::StorCmp::Role::DbicLnk; |
2
|
|
|
|
|
|
|
|
3
|
5
|
|
|
5
|
|
1018
|
use strict; |
|
5
|
|
|
|
|
16
|
|
|
5
|
|
|
|
|
174
|
|
4
|
5
|
|
|
5
|
|
36
|
use warnings; |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
156
|
|
5
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
450
|
use Moose::Role; |
|
5
|
|
|
|
|
456094
|
|
|
5
|
|
|
|
|
47
|
|
7
|
|
|
|
|
|
|
requires 'record_pk'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# Copied from (RapidApp::)Role::DbicLink2 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
|
13
|
5
|
|
|
5
|
|
28093
|
use RapidApp::Util qw(:all); |
|
5
|
|
|
|
|
16
|
|
|
5
|
|
|
|
|
2975
|
|
14
|
5
|
|
|
5
|
|
2612
|
use RapidApp::TableSpec::DbicTableSpec; |
|
5
|
|
|
|
|
20
|
|
|
5
|
|
|
|
|
229
|
|
15
|
5
|
|
|
5
|
|
50
|
use Clone qw(clone); |
|
5
|
|
|
|
|
12
|
|
|
5
|
|
|
|
|
337
|
|
16
|
5
|
|
|
5
|
|
38
|
use Text::Glob qw( match_glob ); |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
219
|
|
17
|
5
|
|
|
5
|
|
3817
|
use Text::TabularDisplay; |
|
5
|
|
|
|
|
6418
|
|
|
5
|
|
|
|
|
211
|
|
18
|
5
|
|
|
5
|
|
37
|
use Time::HiRes qw(gettimeofday tv_interval); |
|
5
|
|
|
|
|
11
|
|
|
5
|
|
|
|
|
51
|
|
19
|
5
|
|
|
5
|
|
3103
|
use RapidApp::Data::Dmap qw(dmap); |
|
5
|
|
|
|
|
16
|
|
|
5
|
|
|
|
|
283
|
|
20
|
5
|
|
|
5
|
|
39
|
use URI::Escape; |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
306
|
|
21
|
5
|
|
|
5
|
|
33
|
use Scalar::Util qw(looks_like_number); |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
198
|
|
22
|
5
|
|
|
5
|
|
2266
|
use Digest::SHA1; |
|
5
|
|
|
|
|
4266
|
|
|
5
|
|
|
|
|
248
|
|
23
|
5
|
|
|
5
|
|
5218
|
use DateTime; |
|
5
|
|
|
|
|
2183007
|
|
|
5
|
|
|
|
|
355
|
|
24
|
|
|
|
|
|
|
require RapidApp::DBIC::Component::TableSpec; |
25
|
|
|
|
|
|
|
|
26
|
5
|
|
|
5
|
|
3014
|
use DBI::Const::GetInfoType '%GetInfoType'; |
|
5
|
|
|
|
|
29981
|
|
|
5
|
|
|
|
|
20132
|
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
if($ENV{DBIC_TRACE}) { |
29
|
|
|
|
|
|
|
debug_around 'DBIx::Class::Storage::DBI::_execute', newline => 1, stack=>20; |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
our $append_exception_title = ''; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# This allows supplying custom BUILD code via a constructor: |
35
|
|
|
|
|
|
|
has 'onBUILD', is => 'ro', isa => 'Maybe[CodeRef]', default => undef; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has 'get_record_display' => ( is => 'ro', isa => 'CodeRef', lazy => 1, default => sub { |
38
|
|
|
|
|
|
|
my $self = shift; |
39
|
|
|
|
|
|
|
return $self->TableSpec->get_Cnf('row_display'); |
40
|
|
|
|
|
|
|
}); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Useful for pages that display only the content of a single database record at a time. |
43
|
|
|
|
|
|
|
# When set to true, rows are limited to "1" in the ResultSet in read_records and the |
44
|
|
|
|
|
|
|
# pager is not used to perform the second query to get the total count |
45
|
|
|
|
|
|
|
has 'single_record_fetch', is => 'ro', isa => 'Bool', default => 0; |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# Colspec attrs can be specified as simple arrayrefs. Defaults to all local columns |
49
|
|
|
|
|
|
|
has 'include_colspec' => ( is => 'ro', isa => 'ArrayRef[Str]', default => sub {['*']} ); |
50
|
|
|
|
|
|
|
has 'relation_sep' => ( is => 'ro', isa => 'Str', default => '__' ); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
has 'updatable_colspec' => ( is => 'ro', isa => 'Maybe[ArrayRef[Str]]', default => undef ); |
53
|
|
|
|
|
|
|
has 'creatable_colspec' => ( is => 'ro', isa => 'Maybe[ArrayRef[Str]]', default => undef ); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# Specify a list of relspecs to enable record destroy anmd specify which related rows |
56
|
|
|
|
|
|
|
# should also be destroyed. For the base rel only, '*', specify other rels by name |
57
|
|
|
|
|
|
|
# NOTE: This is simular in principle, but NOT the same as the colspecs. There is currently |
58
|
|
|
|
|
|
|
# no real logic in this, no wildcard support, etc. It is just a list of relationship names |
59
|
|
|
|
|
|
|
# that will be followed and be deleted along with the base. BE CAREFUL! This will delete whole |
60
|
|
|
|
|
|
|
# sets of related rows. Most of the time you'll only want to put '*' in here |
61
|
|
|
|
|
|
|
has 'destroyable_relspec' => ( is => 'ro', isa => 'Maybe[ArrayRef[Str]]', default => undef ); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# New: List of relationship names to auto-create if they don't exist during an UPDATE |
64
|
|
|
|
|
|
|
# TODO: make this a 'relspec' format like 'destroyable_relspec' above |
65
|
|
|
|
|
|
|
has 'update_create_rels', is => 'ro', isa => 'ArrayRef[Str]', default => sub {[]}; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# These columns will always be fetched regardless of whether or not they were requested |
68
|
|
|
|
|
|
|
# by the client: |
69
|
|
|
|
|
|
|
has 'always_fetch_colspec' => ( is => 'ro', isa => 'Maybe[ArrayRef[Str]]', default => undef ); |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# quicksearch_mode: either 'like' or 'exact' - see chain_Rs_req_quicksearch() |
72
|
|
|
|
|
|
|
# currently any value other than 'exact' is treated like 'like', the default and |
73
|
|
|
|
|
|
|
# original behavior. |
74
|
|
|
|
|
|
|
# TODO: add 'phrases' mode to act like google searches with +/- and quotes around phrases |
75
|
|
|
|
|
|
|
has 'quicksearch_mode', is => 'ro', isa => 'Str', default => 'like'; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Define if the user/client is allowed to specify the quicksearch_mode: |
78
|
|
|
|
|
|
|
has 'allow_set_quicksearch_mode', is => 'ro', isa => 'Bool', default => 1; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# If natural_column_order is true (default) columns will be ordered according to the real |
82
|
|
|
|
|
|
|
# database/schema order, otherwise, order is based on the include_colspec |
83
|
|
|
|
|
|
|
has 'natural_column_order', is => 'ro', isa => 'Bool', default => 1; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# Whether or not to pull the record key from the url/args for the query. |
86
|
|
|
|
|
|
|
# This only makes sense in the context of a single row view, not a set/grid |
87
|
|
|
|
|
|
|
has 'allow_restful_queries', is => 'ro', isa => 'Bool', default => 0; |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# Expose the DatStor 'reload_on_save' option so the user can turn it on |
90
|
|
|
|
|
|
|
# TODO - this is a stop-gap until the DatStor as a separate module design |
91
|
|
|
|
|
|
|
# can be refactored to make it easier to apply store aoptions in general |
92
|
|
|
|
|
|
|
has 'reload_on_save', is => 'ro', isa => 'Bool', default => 0; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# Passed into TableSpec objects -- disables the default behaviour which |
95
|
|
|
|
|
|
|
# transforms the header of related columns by appending the relationship path |
96
|
|
|
|
|
|
|
has 'no_header_transform', is => 'ro', isa => 'Bool', default => 0; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# If set to true, the component will try to close itself after a delete/destroy. Only |
99
|
|
|
|
|
|
|
# makes sense in the context of a single record view, and only works with standard tabs |
100
|
|
|
|
|
|
|
has 'close_on_destroy', is => 'ro', isa => 'Bool', traits => ['ExtProp'], default => 0; |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# If set to true, every time the component is shown (i.e. re-activating the tab) the |
103
|
|
|
|
|
|
|
# store will reload itself to refresh data. |
104
|
|
|
|
|
|
|
has 'reload_on_show', is => 'ro', isa => 'Bool', , traits => ['ExtProp'], default => 0; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Generate a param string unique to this module by URL/path. This only needs to be unique |
107
|
|
|
|
|
|
|
# among modules whose ->content may be rendered within the same request, which is only |
108
|
|
|
|
|
|
|
# being done for good measure |
109
|
|
|
|
|
|
|
has '_rst_qry_param', is => 'ro', isa => 'Str', lazy => 1, default => sub { |
110
|
|
|
|
|
|
|
my $self = shift; |
111
|
|
|
|
|
|
|
join('_', |
112
|
|
|
|
|
|
|
'rst_qry', |
113
|
|
|
|
|
|
|
substr(Digest::SHA1->new->add($self->base_url)->hexdigest, 0, 5) |
114
|
|
|
|
|
|
|
); |
115
|
|
|
|
|
|
|
}; |
116
|
|
|
|
|
|
|
sub _appl_base_params { |
117
|
0
|
|
|
0
|
|
0
|
my ($self, $params) = @_; |
118
|
0
|
|
|
|
|
0
|
my $c = $self->c; |
119
|
|
|
|
|
|
|
|
120
|
0
|
|
|
|
|
0
|
%{$c->req->params} = ( %{$c->req->params}, %$params ); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
121
|
|
|
|
|
|
|
|
122
|
0
|
|
0
|
|
|
0
|
my $baseParams = $self->DataStore->get_extconfig_param('baseParams') || {}; |
123
|
0
|
|
|
|
|
0
|
%$baseParams = ( %$baseParams, %$params ); |
124
|
0
|
|
|
|
|
0
|
$self->DataStore->apply_extconfig( baseParams => $baseParams ); |
125
|
|
|
|
|
|
|
} |
126
|
|
|
|
|
|
|
sub _appl_rst_qry { |
127
|
0
|
|
|
0
|
|
0
|
my ($self, $val) = @_; |
128
|
0
|
|
|
|
|
0
|
$self->_appl_base_params({ $self->_rst_qry_param => $val }); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
sub _retr_rst_qry { |
131
|
8
|
|
|
8
|
|
25
|
my $self = shift; |
132
|
8
|
50
|
|
|
|
51
|
my $c = RapidApp->active_request_context or return undef; |
133
|
8
|
50
|
|
|
|
41
|
my $rst_qry = $c->req->params->{ $self->_rst_qry_param } or return undef; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# Re-apply the rst_qry now to make sure there is not a caching issue |
136
|
|
|
|
|
|
|
# in the DataStore baseParams in case the normal rest logic doesn't |
137
|
|
|
|
|
|
|
# do this, which is the case when launched from a foreign component |
138
|
|
|
|
|
|
|
# by setting rest_args in the stash |
139
|
0
|
|
|
|
|
0
|
$self->_appl_rst_qry( $rst_qry ); |
140
|
|
|
|
|
|
|
|
141
|
0
|
|
|
|
|
0
|
$rst_qry |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
has 'ResultSource' => ( |
146
|
|
|
|
|
|
|
is => 'ro', |
147
|
|
|
|
|
|
|
isa => 'DBIx::Class::ResultSource', |
148
|
|
|
|
|
|
|
required => 1 |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
has 'get_ResultSet' => ( is => 'ro', isa => 'CodeRef', lazy => 1, default => sub { |
152
|
|
|
|
|
|
|
my $self = shift; |
153
|
|
|
|
|
|
|
return sub { $self->ResultSource->resultset }; |
154
|
|
|
|
|
|
|
}); |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub baseResultSet { |
157
|
11
|
|
|
11
|
0
|
36
|
my $self = shift; |
158
|
11
|
|
|
|
|
413
|
return $self->get_ResultSet->(@_); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub _ResultSet { |
162
|
8
|
|
|
8
|
|
31
|
my $self = shift; |
163
|
|
|
|
|
|
|
|
164
|
8
|
|
|
|
|
43
|
my $p = $self->c->req->params; |
165
|
8
|
0
|
33
|
|
|
791
|
if($p->{rs_path} && $p->{rs_method}) { |
166
|
0
|
0
|
|
|
|
0
|
my $Module = $self->get_Module($p->{rs_path}) or die "Failed to get module at $p->{rs_path}"; |
167
|
0
|
|
|
|
|
0
|
return $Module->_resolve_rel_obj_method($p->{rs_method}); |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
8
|
|
|
|
|
58
|
my $Rs = $self->baseResultSet(@_); |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# the order of when this is called is vitally important: |
173
|
8
|
|
|
|
|
4140
|
$self->prepare_rest_request; |
174
|
|
|
|
|
|
|
|
175
|
8
|
50
|
|
|
|
68
|
if(my $rst_qry = $self->_retr_rst_qry) { |
176
|
0
|
|
|
|
|
0
|
my ($key,$val) = split(/\//,$rst_qry,2); |
177
|
0
|
|
|
|
|
0
|
$Rs = $self->chain_Rs_REST($Rs,$key,$val); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
8
|
50
|
|
|
|
152
|
$Rs = $self->ResultSet($Rs) if ($self->can('ResultSet')); |
181
|
8
|
|
|
|
|
31
|
return $Rs; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub chain_Rs_REST { |
185
|
0
|
|
|
0
|
0
|
0
|
my ($self,$Rs,$key,$val) = @_; |
186
|
0
|
0
|
|
|
|
0
|
if ($key =~ /\./) { |
187
|
|
|
|
|
|
|
# if there is a '.' in the key name, assume it means 'rel.col', and |
188
|
|
|
|
|
|
|
# try to add the join for 'rel': |
189
|
0
|
|
|
|
|
0
|
my ($rel) = split(/\./,$key,2); |
190
|
0
|
0
|
|
|
|
0
|
$Rs = $self->_chain_search_rs($Rs,undef,{ join => $rel }) |
191
|
|
|
|
|
|
|
if ($self->ResultSource->has_relationship($rel)); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
else { |
194
|
0
|
|
|
|
|
0
|
$key = 'me.' . $key; |
195
|
|
|
|
|
|
|
} |
196
|
0
|
|
|
|
|
0
|
return $self->_chain_search_rs($Rs,{ $key => $val }); |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
has 'get_CreateData' => ( is => 'ro', isa => 'CodeRef', lazy => 1, default => sub { |
200
|
|
|
|
|
|
|
my $self = shift; |
201
|
|
|
|
|
|
|
return sub { {} }; |
202
|
|
|
|
|
|
|
}); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub baseCreateData { |
205
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
206
|
0
|
|
|
|
|
0
|
return $self->get_CreateData->(@_); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub _CreateData { |
210
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
211
|
0
|
|
|
|
|
0
|
my $data = $self->baseCreateData(@_); |
212
|
0
|
0
|
|
|
|
0
|
$data = $self->CreateData($data) if ($self->can('CreateData')); |
213
|
|
|
|
|
|
|
|
214
|
0
|
0
|
|
|
|
0
|
if(my $lock_keys = $self->_get_rs_lock_keys) { |
215
|
0
|
0
|
|
|
|
0
|
$data = { %{ $data || {} }, %$lock_keys } |
|
0
|
|
|
|
|
0
|
|
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
0
|
|
|
|
|
0
|
return $data; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
#sub _ResultSet { |
222
|
|
|
|
|
|
|
# my $self = shift; |
223
|
|
|
|
|
|
|
# my $Rs = $self->ResultSource->resultset; |
224
|
|
|
|
|
|
|
# $Rs = $self->ResultSet($Rs) if ($self->can('ResultSet')); |
225
|
|
|
|
|
|
|
# return $Rs; |
226
|
|
|
|
|
|
|
#} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
has 'ResultClass' => ( is => 'ro', lazy_build => 1 ); |
229
|
|
|
|
|
|
|
sub _build_ResultClass { |
230
|
92
|
|
|
92
|
|
267
|
my $self = shift; |
231
|
92
|
|
|
|
|
2994
|
my $source_name = $self->ResultSource->source_name; |
232
|
92
|
|
|
|
|
2749
|
return $self->ResultSource->schema->class($source_name); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
has 'TableSpec' => ( is => 'ro', isa => 'RapidApp::TableSpec', lazy_build => 1 ); |
236
|
|
|
|
|
|
|
sub _build_TableSpec { |
237
|
92
|
|
|
92
|
|
248
|
my $self = shift; |
238
|
|
|
|
|
|
|
|
239
|
92
|
|
|
|
|
3065
|
my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($self->ResultClass->table); |
240
|
92
|
|
|
|
|
3305
|
my %opt = ( |
241
|
|
|
|
|
|
|
name => $table, |
242
|
|
|
|
|
|
|
relation_sep => $self->relation_sep, |
243
|
|
|
|
|
|
|
ResultSource => $self->ResultSource, |
244
|
|
|
|
|
|
|
include_colspec => $self->include_colspec, |
245
|
|
|
|
|
|
|
no_header_transform => $self->no_header_transform |
246
|
|
|
|
|
|
|
); |
247
|
|
|
|
|
|
|
|
248
|
92
|
100
|
|
|
|
3230
|
$opt{updatable_colspec} = $self->updatable_colspec if (defined $self->updatable_colspec); |
249
|
92
|
100
|
|
|
|
3147
|
$opt{creatable_colspec} = $self->creatable_colspec if (defined $self->creatable_colspec); |
250
|
92
|
50
|
|
|
|
3471
|
$opt{always_fetch_colspec} = $self->always_fetch_colspec if (defined $self->always_fetch_colspec); |
251
|
|
|
|
|
|
|
|
252
|
92
|
50
|
33
|
|
|
2612
|
if (!exists $opt{cache} && $self->app->rapidApp->use_cache) { |
253
|
92
|
|
|
|
|
2243
|
$opt{cache} = $self->app->rapidApp->cache; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
92
|
|
|
|
|
3559
|
my $TableSpec = RapidApp::TableSpec::DbicTableSpec->new(%opt); |
257
|
|
|
|
|
|
|
|
258
|
92
|
50
|
|
|
|
3304
|
$TableSpec->apply_natural_column_order if ($self->natural_column_order); |
259
|
|
|
|
|
|
|
|
260
|
92
|
|
|
|
|
3195
|
return $TableSpec; |
261
|
|
|
|
|
|
|
#return RapidApp::TableSpec->with_traits('RapidApp::TableSpec::Role::DBIC')->new(%opt); |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
has 'record_pk' => ( is => 'ro', isa => 'Str', default => '___record_pk' ); |
266
|
|
|
|
|
|
|
has 'primary_columns_sep' => ( is => 'ro', isa => 'Str', default => '~$~' ); |
267
|
|
|
|
|
|
|
has 'primary_columns' => ( is => 'ro', isa => 'ArrayRef[Str]', lazy => 1, default => sub { |
268
|
|
|
|
|
|
|
my $self = shift; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# If the db has no primary columns, then we have to use ALL the columns: |
271
|
|
|
|
|
|
|
unless ($self->ResultSource->primary_columns > 0) { |
272
|
|
|
|
|
|
|
my $class = $self->ResultSource->schema->class($self->ResultSource->source_name); |
273
|
|
|
|
|
|
|
local $SIG{__WARN__} = sub {}; # GitHub Issue #167 - TODO/FIXME |
274
|
|
|
|
|
|
|
$class->set_primary_key( $self->ResultSource->columns ); |
275
|
|
|
|
|
|
|
$self->ResultSource->set_primary_key( $self->ResultSource->columns ); |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
my @cols = $self->ResultSource->primary_columns; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
$self->apply_extconfig( primary_columns => [ $self->record_pk, @cols ] ); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
return \@cols; |
283
|
|
|
|
|
|
|
}); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub generate_record_pk_value { |
287
|
10
|
|
|
10
|
0
|
34
|
my $self = shift; |
288
|
10
|
|
|
|
|
22
|
my $data = shift; |
289
|
10
|
50
|
|
|
|
52
|
die "generate_record_pk_value(): expected hashref arg" unless (ref($data) eq 'HASH'); |
290
|
|
|
|
|
|
|
return join( |
291
|
|
|
|
|
|
|
$self->primary_columns_sep, |
292
|
|
|
|
|
|
|
#map { defined $data->{$_} ? "'" . $data->{$_} . "'" : 'undef' } @{$self->primary_columns} |
293
|
10
|
50
|
|
|
|
445
|
map { defined $data->{$_} ? $data->{$_} : 'undef' } @{$self->primary_columns} |
|
10
|
|
|
|
|
314
|
|
|
10
|
|
|
|
|
341
|
|
294
|
|
|
|
|
|
|
); |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# reverse generate_record_pk_value: |
298
|
|
|
|
|
|
|
sub record_pk_cond { |
299
|
6
|
|
|
6
|
0
|
18
|
my $self = shift; |
300
|
6
|
|
|
|
|
19
|
my $value = shift; |
301
|
|
|
|
|
|
|
|
302
|
6
|
|
|
|
|
247
|
my $sep = quotemeta $self->primary_columns_sep; |
303
|
6
|
|
|
|
|
60
|
my @parts = split(/${sep}/,$value); |
304
|
|
|
|
|
|
|
|
305
|
6
|
|
|
|
|
21
|
my %cond = (); |
306
|
6
|
|
|
|
|
17
|
foreach my $col (@{$self->primary_columns}) { |
|
6
|
|
|
|
|
214
|
|
307
|
6
|
|
|
|
|
21
|
my $val = shift @parts; |
308
|
6
|
50
|
|
|
|
29
|
if ($val eq 'undef') { |
309
|
0
|
|
|
|
|
0
|
$val = undef; |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
else { |
312
|
6
|
|
|
|
|
34
|
$val =~ s/^\'//; |
313
|
6
|
|
|
|
|
22
|
$val =~ s/\'$//; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
# To force an *exact* match when col is a number, have to use LIKE because of the problem described here: |
316
|
|
|
|
|
|
|
#http://stackoverflow.com/questions/8570884/mysql-where-exact-match |
317
|
|
|
|
|
|
|
# Otherwise '1833sdfsdf' will match just like '1833'. But LIKE is slow!!! This is lame! |
318
|
|
|
|
|
|
|
#$cond{'me.' . $col} = { 'LIKE' => $val }; |
319
|
6
|
|
|
|
|
35
|
$cond{'me.' . $col} = $val; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
6
|
|
|
|
|
48
|
return \%cond; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# --- Handle RESTful URLs - convert 'id/1234' into '?___record_pk=1234' |
327
|
|
|
|
|
|
|
#has 'restful_record_pk_alias', is => 'ro', isa => 'Str', default => '_id'; |
328
|
|
|
|
|
|
|
sub prepare_rest_request { |
329
|
8
|
|
|
8
|
0
|
28
|
my $self = shift; |
330
|
8
|
50
|
|
|
|
396
|
return unless ($self->allow_restful_queries); |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# New: allow override pf rest args from stash: |
333
|
0
|
|
|
|
|
0
|
my $stash_args = $self->c->stash->{rest_args}; |
334
|
0
|
0
|
|
|
|
0
|
my @args = $stash_args ? @$stash_args : $self->local_args; |
335
|
|
|
|
|
|
|
|
336
|
0
|
|
|
|
|
0
|
$_ = uri_unescape($_) for (@args); |
337
|
|
|
|
|
|
|
|
338
|
0
|
|
|
|
|
0
|
my @rargs = reverse @args; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# ignore paths that match store CRUD actions (store/create, store/read, store/update or store/destroy) |
341
|
|
|
|
|
|
|
# (TODO: what happens on the off chance that there is a key named 'store' and a value named 'read'?) |
342
|
0
|
|
|
|
|
0
|
my @crud = qw(create read update destroy); |
343
|
0
|
|
|
|
|
0
|
my %crudI = map {$_=>1} @crud; |
|
0
|
|
|
|
|
0
|
|
344
|
|
|
|
|
|
|
return if ( |
345
|
|
|
|
|
|
|
$rargs[0] && $rargs[1] && |
346
|
|
|
|
|
|
|
$rargs[1] eq 'store' && |
347
|
0
|
0
|
0
|
|
|
0
|
$crudI{$rargs[0]} |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
348
|
|
|
|
|
|
|
); |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
# -- peel off the 'rel' (relationship) args if present: |
351
|
0
|
|
|
|
|
0
|
my $rel; |
352
|
0
|
0
|
|
|
|
0
|
if(scalar @args > 2) { |
353
|
0
|
0
|
0
|
|
|
0
|
if(lc($rargs[1]) eq 'rel' || lc($rargs[1]) eq 'rs') { |
354
|
0
|
|
|
|
|
0
|
$rel = pop @args; |
355
|
0
|
|
|
|
|
0
|
pop @args; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
# -- |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# --- Handle and assume extra args are values containing '/' |
361
|
0
|
0
|
|
|
|
0
|
if(scalar @args > 1) { |
362
|
0
|
|
|
|
|
0
|
my @newargs = (shift @args); |
363
|
0
|
0
|
0
|
|
|
0
|
if (scalar @args > 0 && $self->ResultSource->has_column($newargs[0])) { |
364
|
0
|
|
|
|
|
0
|
push @newargs, join('/',@args); |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
else { |
367
|
0
|
|
|
|
|
0
|
@newargs = (join('/',@newargs,@args)); |
368
|
|
|
|
|
|
|
} |
369
|
0
|
|
|
|
|
0
|
@args = @newargs; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
# --- |
372
|
|
|
|
|
|
|
|
373
|
0
|
0
|
|
|
|
0
|
return unless defined $args[0]; |
374
|
0
|
|
|
|
|
0
|
my $key = "$args[0]"; |
375
|
0
|
|
|
|
|
0
|
my $val = $args[1]; |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# Ignore paths that are submodules or actions: |
378
|
0
|
0
|
0
|
|
|
0
|
return if (exists $self->modules_obj->{$key} || $self->has_action($key)); |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# if there was only 1 argument, treat it as the value and set the default key/pk: |
381
|
0
|
0
|
|
|
|
0
|
unless (defined $val) { |
382
|
0
|
|
|
|
|
0
|
$val = $args[0]; |
383
|
0
|
|
|
0
|
|
0
|
my $rest_key_column = try{$self->ResultClass->getRestKey}; |
|
0
|
|
|
|
|
0
|
|
384
|
0
|
|
0
|
|
|
0
|
$key = $rest_key_column || $self->record_pk; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# This should never happen any more (see "Handle and assume..." above): |
388
|
0
|
0
|
|
|
|
0
|
die usererr "Too many args in RESTful URL (" . join('/',@args) . ") - should be 2 (i.e. 'id/1234')" |
389
|
|
|
|
|
|
|
if(scalar @args > 2); |
390
|
|
|
|
|
|
|
|
391
|
0
|
0
|
|
|
|
0
|
return $self->redirect_handle_rest_rel_request($key,$val,$rel) if ($rel); |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# Apply default tabTitle: (see also 'getTabTitle' in DbicRowPage) |
394
|
0
|
0
|
|
|
|
0
|
$self->apply_extconfig( tabTitle => ($key eq $self->record_pk ? 'Id' : $key ) . '/' . $val ); |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# --- |
397
|
|
|
|
|
|
|
# Update both the params of the active request, in place, as well as updating the baseParams |
398
|
|
|
|
|
|
|
# of the store for the subsequent read request: |
399
|
|
|
|
|
|
|
# TODO: '___record_pk' and 'rest_query' params are handled in different places in the subsequent |
400
|
|
|
|
|
|
|
# read request. '___record_pk' pre-dates the REST functionality and is only handled in DbicAppPropertyPage |
401
|
|
|
|
|
|
|
# (see the req_Row and and supplied_id methods in that class) while 'rest_query' is handled by |
402
|
|
|
|
|
|
|
# all modules with the DbicLink2 role. Need to consolidate these in DbicLink2 so this all happens in |
403
|
|
|
|
|
|
|
# the same place |
404
|
0
|
0
|
|
|
|
0
|
if($key eq $self->record_pk) { |
405
|
0
|
|
|
|
|
0
|
$self->_appl_base_params({$key => $val}); |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
else { |
408
|
0
|
|
|
|
|
0
|
$self->_appl_rst_qry( join('/',$key,$val) ); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
# --- |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub restGetRow { |
416
|
0
|
|
|
0
|
0
|
0
|
my ($self,$key,$val) = @_; |
417
|
|
|
|
|
|
|
|
418
|
0
|
|
|
|
|
0
|
my $Rs = $self->chain_Rs_REST($self->baseResultSet,$key,$val); |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# TODO: currently duplicated in DbicAppPropertyPage... it should defer to here |
421
|
0
|
|
|
|
|
0
|
my $count = $Rs->count; |
422
|
|
|
|
|
|
|
|
423
|
0
|
0
|
|
|
|
0
|
die usererr "Record not found by '$key/$val'", title => 'Record not found' |
424
|
|
|
|
|
|
|
unless ($count); |
425
|
|
|
|
|
|
|
|
426
|
0
|
0
|
|
|
|
0
|
die usererr $count . " records match '$key/$val'", title => 'Multiple records match' |
427
|
|
|
|
|
|
|
if($count > 1); |
428
|
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
0
|
return $Rs->first; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# This is designed to be called from *another* module to resolve a ResultSet |
433
|
|
|
|
|
|
|
# object via arbitrary 'rs_method' path spec |
434
|
|
|
|
|
|
|
sub _resolve_rel_obj_method { |
435
|
0
|
|
|
0
|
|
0
|
my ($self, $rs_method) = @_; |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# New: Parse like this in case the middle $val contains '/' |
438
|
0
|
|
|
|
|
0
|
my @parts = split('/',$rs_method); |
439
|
0
|
|
|
|
|
0
|
my $key = shift @parts; |
440
|
0
|
|
|
|
|
0
|
my $rel = pop @parts; |
441
|
0
|
|
|
|
|
0
|
my $val = join('/',@parts); |
442
|
|
|
|
|
|
|
#my ($key,$val,$rel) = split('/',$rs_method,3); |
443
|
|
|
|
|
|
|
|
444
|
0
|
|
|
|
|
0
|
my $Row = $self->restGetRow($key,$val); |
445
|
0
|
0
|
|
|
|
0
|
die usererr "No such relationship $rel at ''$rs_method''" unless ($Row->has_relationship($rel)); |
446
|
0
|
0
|
|
|
|
0
|
return wantarray ? (scalar $Row->$rel, $Row) : $Row->$rel; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
sub redirect_handle_rest_rel_request { |
450
|
0
|
|
|
0
|
0
|
0
|
my ($self,$key,$val,$rel) = @_; |
451
|
0
|
|
|
|
|
0
|
my $c = $self->c; |
452
|
|
|
|
|
|
|
|
453
|
0
|
|
|
|
|
0
|
my $mth_path = join('/',$key,$val,$rel); |
454
|
0
|
|
|
|
|
0
|
my ($RelObj, $Row) = $self->_resolve_rel_obj_method($mth_path); |
455
|
0
|
|
|
|
|
0
|
my $Src = $RelObj->result_source; |
456
|
0
|
|
|
|
|
0
|
my $class = $Src->schema->class($Src->source_name); |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
$c->stash->{apply_extconfig} = { |
459
|
0
|
|
|
|
|
0
|
tabTitle => "[$key/$val] $rel" |
460
|
|
|
|
|
|
|
}; |
461
|
|
|
|
|
|
|
|
462
|
0
|
0
|
|
|
|
0
|
if($RelObj->isa('DBIx::Class::ResultSet')) { |
463
|
0
|
|
|
0
|
|
0
|
my $url = try{$class->TableSpec_get_conf('open_url_multi')} |
464
|
0
|
0
|
|
|
|
0
|
or die usererr "No path (open_url_multi) defined to render Result Class: $class"; |
465
|
|
|
|
|
|
|
|
466
|
0
|
|
|
|
|
0
|
my $p = { |
467
|
|
|
|
|
|
|
rs_path => $self->module_path, |
468
|
|
|
|
|
|
|
rs_method => join('/',$key,$val,$rel) |
469
|
|
|
|
|
|
|
}; |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
# --- |
472
|
|
|
|
|
|
|
# New: For the case of a multi-relationship, attempt to resolve the reverse |
473
|
|
|
|
|
|
|
# relationship (i.e. the belongs_to) and set the new 'rs_lock_keys' info to |
474
|
|
|
|
|
|
|
# declare to the target Module the fk value that must be maintained and |
475
|
|
|
|
|
|
|
# enforced for this relationship. This is then used when adding new records |
476
|
|
|
|
|
|
|
# and editability of the linking relationship is disabled. |
477
|
0
|
0
|
|
|
|
0
|
if(my $rev_rel_info = $Row->result_source->reverse_relationship_info($rel)) { |
478
|
0
|
|
|
|
|
0
|
my ($rev_rel, $info) = %$rev_rel_info; |
479
|
0
|
0
|
0
|
|
|
0
|
if($info && $info->{cond}) { |
480
|
0
|
|
|
|
|
0
|
require RapidApp::DBIC::Component::TableSpec; |
481
|
|
|
|
|
|
|
my $cdta = RapidApp::DBIC::Component::TableSpec |
482
|
0
|
|
0
|
|
|
0
|
->parse_relationship_cond($info->{cond}) || {}; |
483
|
0
|
|
|
|
|
0
|
my @pks = $Row->result_source->primary_columns; |
484
|
0
|
0
|
0
|
|
|
0
|
if(scalar(@pks) == 1 && $cdta->{self} && $cdta->{foreign} && $pks[0] eq $cdta->{foreign}) { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
485
|
|
|
|
|
|
|
$p->{rs_lock_keys} = $self->json->encode({ |
486
|
0
|
|
|
|
|
0
|
$cdta->{self} => $val, |
487
|
|
|
|
|
|
|
$rev_rel => $val, |
488
|
|
|
|
|
|
|
}); |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
# --- |
493
|
|
|
|
|
|
|
|
494
|
0
|
|
|
|
|
0
|
%{$c->req->params} = ( %$p, base_params => $self->json->encode( $p ) ); |
|
0
|
|
|
|
|
0
|
|
495
|
0
|
|
|
|
|
0
|
$c->root_module_controller->approot($c,$url); |
496
|
0
|
|
|
|
|
0
|
return $c->detach; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
else { |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# New: here we are actually dispatching to the page for the single rel, but still |
501
|
|
|
|
|
|
|
# within the rest URL of the rel path. Ideally, for this case we would *redirect* |
502
|
|
|
|
|
|
|
# to the actual REST URL for thsi object, whatever it may be. In order to do this, |
503
|
|
|
|
|
|
|
# support for redirects needs to be added to the autopanel/hashnav stuff on the |
504
|
|
|
|
|
|
|
# client side. In the meantime, rendering the real/actual row page, albeit at an |
505
|
|
|
|
|
|
|
# alias (but still totally valid) url path is the best choice |
506
|
|
|
|
|
|
|
|
507
|
0
|
|
|
0
|
|
0
|
my $url = try{$RelObj->getRestPath}; |
|
0
|
|
|
|
|
0
|
|
508
|
0
|
0
|
|
|
|
0
|
if($url) { |
509
|
|
|
|
|
|
|
# Simulate the rest_args for proper handling of the remote DbicLink |
510
|
|
|
|
|
|
|
# request to operate under the current, alias URL: |
511
|
0
|
|
|
|
|
0
|
$self->c->stash->{rest_args} = [$RelObj->getRestKey,$RelObj->getRestKeyVal]; |
512
|
0
|
|
|
|
|
0
|
$c->root_module_controller->approot($c,$url); |
513
|
0
|
|
|
|
|
0
|
return $c->detach; |
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
else { |
516
|
|
|
|
|
|
|
# This is just a fallback - TODO: use a better error msg... |
517
|
|
|
|
|
|
|
die usererr rawhtml join('', |
518
|
|
|
|
|
|
|
"Relationship at '$mth_path' is not a ResultSet, it is a Row", |
519
|
0
|
|
|
0
|
|
0
|
try{join(''," (", |
520
|
|
|
|
|
|
|
'<i><b style="color:darkblue;font-size:0.9em;">', |
521
|
|
|
|
|
|
|
$RelObj->displayWithLink,'</b></i>', |
522
|
|
|
|
|
|
|
')')}, |
523
|
0
|
|
|
|
|
0
|
), title => 'Not a multi relationship'; |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
sub _get_rs_lock_keys { |
530
|
24
|
|
|
24
|
|
60
|
my $self = shift; |
531
|
|
|
|
|
|
|
|
532
|
24
|
50
|
|
|
|
115
|
my $c = RapidApp->active_request_context or return undef; |
533
|
24
|
50
|
|
|
|
109
|
my $lk_enc = $c->req->params->{rs_lock_keys} or return undef; |
534
|
0
|
|
|
0
|
|
0
|
try{$self->json->decode($lk_enc)} |
535
|
0
|
|
|
|
|
0
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
before 'store_init_onrequest' => sub { |
539
|
|
|
|
|
|
|
my $self = shift; |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
if(my $lock_keys = $self->_get_rs_lock_keys) { |
542
|
|
|
|
|
|
|
my @cols = (); |
543
|
|
|
|
|
|
|
for my $name (keys %$lock_keys) { |
544
|
|
|
|
|
|
|
my $Column = $self->get_column($name) or next; |
545
|
|
|
|
|
|
|
push @cols, $name; |
546
|
|
|
|
|
|
|
# Set the default value of the editor to the locked key value, and then |
547
|
|
|
|
|
|
|
# set to 'disabled' to prevent the user from changing it |
548
|
|
|
|
|
|
|
my $editor = $Column->{editor} or next; |
549
|
|
|
|
|
|
|
$editor->{value} = $lock_keys->{$name}; |
550
|
|
|
|
|
|
|
$editor->{disabled} = \1; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
if(scalar(@cols) > 0) { |
553
|
|
|
|
|
|
|
# Sets each locked fk to be the first column (top of teh add form), and not editable |
554
|
|
|
|
|
|
|
$self->apply_columns_ordered(0, map { $_ => { |
555
|
|
|
|
|
|
|
allow_edit => \0 |
556
|
|
|
|
|
|
|
}} @cols ); |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
} |
559
|
|
|
|
|
|
|
}; |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
# --- |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
0
|
0
|
|
sub BUILD {} |
565
|
|
|
|
|
|
|
around 'BUILD' => sub { &DbicLink_around_BUILD(@_) }; |
566
|
|
|
|
|
|
|
sub DbicLink_around_BUILD { |
567
|
92
|
|
|
92
|
0
|
225
|
my $orig = shift; |
568
|
92
|
|
|
|
|
194
|
my $self = shift; |
569
|
|
|
|
|
|
|
|
570
|
92
|
50
|
|
|
|
462
|
die "FATAL: DbicLink and DbicLink2 cannot both be loaded" if ($self->does('RapidApp::Role::DbicLink')); |
571
|
|
|
|
|
|
|
|
572
|
92
|
100
|
|
|
|
75865
|
$self->accept_subargs(1) if ($self->allow_restful_queries); |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
# Disable editing on columns that aren't updatable: |
575
|
|
|
|
|
|
|
#$self->apply_except_colspec_columns($self->TableSpec->updatable_colspec => { |
576
|
|
|
|
|
|
|
# editor => '' |
577
|
|
|
|
|
|
|
#}); |
578
|
|
|
|
|
|
|
|
579
|
92
|
|
|
|
|
2846
|
$self->apply_columns( $self->record_pk => { |
580
|
|
|
|
|
|
|
no_column => \1, |
581
|
|
|
|
|
|
|
no_multifilter => \1, |
582
|
|
|
|
|
|
|
no_quick_search => \1 |
583
|
|
|
|
|
|
|
}); |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
# Hide any extra colspec columns that were only added for relationship |
586
|
|
|
|
|
|
|
# columns: |
587
|
|
|
|
|
|
|
#$self->apply_colspec_columns($self->TableSpec->added_relationship_column_relspecs, |
588
|
|
|
|
|
|
|
# no_column => \1, |
589
|
|
|
|
|
|
|
# no_multifilter => \1, |
590
|
|
|
|
|
|
|
# no_quick_search => \1 |
591
|
|
|
|
|
|
|
#); |
592
|
|
|
|
|
|
|
|
593
|
92
|
|
|
|
|
731
|
$self->$orig(@_); |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
# init primary columns: |
596
|
92
|
|
|
|
|
3156
|
$self->primary_columns; |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
# TODO: find out why this option doesn't work when applied via other, newer config mechanisms: |
599
|
92
|
|
|
|
|
615
|
$self->apply_store_config( |
600
|
|
|
|
|
|
|
remoteSort => \1 |
601
|
|
|
|
|
|
|
); |
602
|
|
|
|
|
|
|
|
603
|
92
|
50
|
|
|
|
3235
|
$self->apply_extconfig( |
604
|
|
|
|
|
|
|
remote_columns => \1, |
605
|
|
|
|
|
|
|
loadMask => \1, |
606
|
|
|
|
|
|
|
quicksearch_mode => $self->quicksearch_mode, |
607
|
|
|
|
|
|
|
allow_set_quicksearch_mode => $self->allow_set_quicksearch_mode ? \1 : \0 |
608
|
|
|
|
|
|
|
); |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
# This allows supplying custom BUILD code via a constructor: |
612
|
92
|
50
|
|
|
|
3554
|
$self->onBUILD->($self) if ($self->onBUILD); |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
sub apply_colspec_columns { |
616
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
617
|
0
|
|
|
|
|
0
|
my $colspec = shift; |
618
|
0
|
0
|
|
|
|
0
|
my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref |
|
0
|
|
|
|
|
0
|
|
619
|
|
|
|
|
|
|
|
620
|
0
|
|
|
|
|
0
|
my @colspecs = ( $colspec ); |
621
|
0
|
0
|
|
|
|
0
|
@colspecs = @$colspec if (ref($colspec) eq 'ARRAY'); |
622
|
|
|
|
|
|
|
|
623
|
0
|
|
|
|
|
0
|
my @columns = $self->TableSpec->get_colspec_column_names(@colspecs); |
624
|
0
|
|
|
|
|
0
|
my %apply = map { $_ => { %opt } } @columns; |
|
0
|
|
|
|
|
0
|
|
625
|
0
|
|
|
|
|
0
|
$self->apply_columns(%apply); |
626
|
|
|
|
|
|
|
} |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
# Apply to all columns except those matching colspec: |
629
|
|
|
|
|
|
|
sub apply_except_colspec_columns { |
630
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
631
|
0
|
|
|
|
|
0
|
my $colspec = shift; |
632
|
0
|
0
|
|
|
|
0
|
my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref |
|
0
|
|
|
|
|
0
|
|
633
|
|
|
|
|
|
|
|
634
|
0
|
|
|
|
|
0
|
my @colspecs = ( $colspec ); |
635
|
0
|
0
|
|
|
|
0
|
@colspecs = @$colspec if (ref($colspec) eq 'ARRAY'); |
636
|
|
|
|
|
|
|
|
637
|
0
|
|
|
|
|
0
|
my @columns = $self->TableSpec->get_except_colspec_column_names(@colspecs); |
638
|
0
|
|
|
|
|
0
|
my %apply = map { $_ => { %opt } } @columns; |
|
0
|
|
|
|
|
0
|
|
639
|
0
|
|
|
|
|
0
|
$self->apply_columns(%apply); |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
sub delete_colspec_columns { |
643
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
644
|
0
|
0
|
|
|
|
0
|
my @colspecs = (ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_; |
|
0
|
|
|
|
|
0
|
|
645
|
|
|
|
|
|
|
|
646
|
0
|
|
|
|
|
0
|
my @columns = $self->TableSpec->get_colspec_column_names(@colspecs); |
647
|
0
|
|
|
|
|
0
|
return $self->delete_columns(@columns); |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
# Delete all columns except those matching colspec: |
651
|
|
|
|
|
|
|
sub delete_except_colspec_columns { |
652
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
653
|
0
|
0
|
|
|
|
0
|
my @colspecs = (ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_; |
|
0
|
|
|
|
|
0
|
|
654
|
|
|
|
|
|
|
|
655
|
0
|
0
|
|
|
|
0
|
die "delete_except_colspec_columns: no colspecs supplied" unless (@colspecs > 0); |
656
|
|
|
|
|
|
|
|
657
|
0
|
|
|
|
|
0
|
my @columns = $self->TableSpec->get_except_colspec_column_names(@colspecs); |
658
|
0
|
|
|
|
|
0
|
return $self->delete_columns(@columns); |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
sub apply_except_colspec_columns_ordered { |
662
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
663
|
0
|
|
|
|
|
0
|
my $indx = shift; |
664
|
0
|
|
|
|
|
0
|
my $colspec = shift; |
665
|
0
|
0
|
|
|
|
0
|
my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref |
|
0
|
|
|
|
|
0
|
|
666
|
|
|
|
|
|
|
|
667
|
0
|
|
|
|
|
0
|
my @colspecs = ( $colspec ); |
668
|
0
|
0
|
|
|
|
0
|
@colspecs = @$colspec if (ref($colspec) eq 'ARRAY'); |
669
|
|
|
|
|
|
|
|
670
|
0
|
|
|
|
|
0
|
my @columns = $self->TableSpec->get_except_colspec_column_names(@colspecs); |
671
|
0
|
|
|
|
|
0
|
my %apply = map { $_ => { %opt } } grep { exists $self->columns->{$_} } @columns; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
672
|
0
|
|
|
|
|
0
|
$self->apply_columns_ordered($indx,%apply); |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
sub get_read_records_Rs { |
677
|
8
|
|
|
8
|
0
|
29
|
my $self = shift; |
678
|
8
|
|
33
|
|
|
35
|
my $params = shift || $self->c->req->params; |
679
|
|
|
|
|
|
|
|
680
|
8
|
|
|
|
|
72
|
my $Rs = $self->_ResultSet; |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
# Apply base Attrs: |
683
|
8
|
|
|
|
|
73
|
$Rs = $self->chain_Rs_req_base_Attr($Rs,$params); |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
# Apply id_in search: |
686
|
8
|
|
|
|
|
5769
|
$Rs = $self->chain_Rs_req_id_in($Rs,$params); |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
# Apply explicit resultset: |
689
|
8
|
|
|
|
|
1120
|
$Rs = $self->chain_Rs_req_explicit_resultset($Rs,$params); |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
# Apply quicksearch: |
692
|
8
|
|
|
|
|
2937
|
$Rs = $self->chain_Rs_req_quicksearch($Rs,$params); |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
# Apply multifilter: |
695
|
8
|
|
|
|
|
53
|
$Rs = $self->chain_Rs_req_multifilter($Rs,$params); |
696
|
|
|
|
|
|
|
|
697
|
8
|
|
|
|
|
24
|
return $Rs; |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
sub read_records { |
701
|
8
|
|
|
8
|
0
|
29
|
my $self = shift; |
702
|
8
|
|
33
|
|
|
41
|
my $params = shift || $self->c->req->params; |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
## --------- |
705
|
|
|
|
|
|
|
## Experimental override to force all joins to be LEFT joins, since in the |
706
|
|
|
|
|
|
|
## context of the grid, it is never helpful to inner join which can cause |
707
|
|
|
|
|
|
|
## rows to not show up when the foreign key isn't found, which is never what |
708
|
|
|
|
|
|
|
## we want to happen - TODO: add test cases for this |
709
|
5
|
|
|
5
|
|
65
|
no warnings 'redefine'; |
|
5
|
|
|
|
|
14
|
|
|
5
|
|
|
|
|
52150
|
|
710
|
8
|
|
|
|
|
37
|
my $orig_resolve_join = \&DBIx::Class::ResultSource::_resolve_join; |
711
|
|
|
|
|
|
|
local *DBIx::Class::ResultSource::_resolve_join = sub { |
712
|
0
|
|
|
0
|
|
0
|
my ($self, $join, $alias, $seen, $jpath, $parent_force_left) = @_; |
713
|
0
|
|
|
|
|
0
|
return $orig_resolve_join->($self, $join, $alias, $seen, $jpath, 1) |
714
|
8
|
|
|
|
|
179
|
}; |
715
|
|
|
|
|
|
|
# for more info, see the thread/convo on github: |
716
|
|
|
|
|
|
|
# https://github.com/vanstyn/RapidApp/commit/cab4a6732 |
717
|
|
|
|
|
|
|
## --------- |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
|
720
|
8
|
|
|
|
|
69
|
my $Rs = $self->get_read_records_Rs($params); |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
# -- Github Issue #10 - SQLite-specific fix -- |
723
|
|
|
|
|
|
|
local $Rs->result_source->storage->dbh |
724
|
8
|
|
|
|
|
56
|
->{sqlite_see_if_its_a_number} = 1; |
725
|
|
|
|
|
|
|
# -- |
726
|
|
|
|
|
|
|
|
727
|
8
|
50
|
|
|
|
4594
|
$Rs = $self->_chain_search_rs($Rs,{},{rows => 1}) if ($self->single_record_fetch); |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
# don't use Row objects |
730
|
8
|
|
|
|
|
57
|
my $Rs2 = $self->_chain_search_rs($Rs,undef, { result_class => 'DBIx::Class::ResultClass::HashRefInflator' }); |
731
|
|
|
|
|
|
|
|
732
|
8
|
|
|
|
|
54427
|
my $rows; |
733
|
|
|
|
|
|
|
try { |
734
|
8
|
|
|
8
|
|
504
|
my $start = [gettimeofday]; |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
# ----- |
737
|
8
|
|
|
|
|
80
|
$rows = [ $self->rs_all($Rs2) ]; |
738
|
|
|
|
|
|
|
#Hard coded munger for record_pk: |
739
|
8
|
|
|
|
|
37
|
foreach my $row (@$rows) { $row->{$self->record_pk} = $self->generate_record_pk_value($row); } |
|
7
|
|
|
|
|
60
|
|
740
|
8
|
|
|
|
|
64
|
$self->apply_first_records($Rs2,$rows,$params); |
741
|
|
|
|
|
|
|
# ----- |
742
|
|
|
|
|
|
|
|
743
|
8
|
|
|
|
|
58
|
my $elapsed = tv_interval($start); |
744
|
8
|
|
|
|
|
376
|
$self->c->stash->{query_time} = sprintf('%.2f',$elapsed) . 's'; |
745
|
|
|
|
|
|
|
} |
746
|
|
|
|
|
|
|
catch { |
747
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
748
|
0
|
|
|
|
|
0
|
$self->handle_dbic_exception($err); |
749
|
8
|
|
|
|
|
118
|
}; |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
# Now calculate a total, for the grid to display the number of available pages |
752
|
8
|
|
|
|
|
1051
|
my $total; |
753
|
|
|
|
|
|
|
try { |
754
|
8
|
|
|
8
|
|
433
|
$total = $self->rs_count($Rs2,$params); |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
catch { |
757
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
758
|
0
|
|
|
|
|
0
|
local $append_exception_title = '(total count)'; |
759
|
0
|
|
|
|
|
0
|
$self->handle_dbic_exception($err); |
760
|
8
|
|
|
|
|
80
|
}; |
761
|
|
|
|
|
|
|
|
762
|
8
|
|
|
|
|
830
|
my $ret = { |
763
|
|
|
|
|
|
|
rows => $rows, |
764
|
|
|
|
|
|
|
results => $total, |
765
|
|
|
|
|
|
|
query_time => $self->query_time |
766
|
|
|
|
|
|
|
}; |
767
|
|
|
|
|
|
|
|
768
|
8
|
50
|
|
|
|
285
|
$self->calculate_column_summaries($ret,$Rs,$params) unless($self->single_record_fetch); |
769
|
|
|
|
|
|
|
|
770
|
8
|
|
|
|
|
51
|
return $ret; |
771
|
|
|
|
|
|
|
} |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
# If optional param 'first_records_cond' was supplied, a second query (sub-set of the original) |
775
|
|
|
|
|
|
|
# is ran and matching rows are moved to the top of the list of rows |
776
|
|
|
|
|
|
|
sub apply_first_records { |
777
|
8
|
|
|
8
|
0
|
35
|
my ($self,$Rs,$rows,$params) = @_; |
778
|
8
|
50
|
33
|
|
|
75
|
return unless ($params && $params->{first_records_cond}); |
779
|
|
|
|
|
|
|
|
780
|
0
|
|
|
|
|
0
|
my $cond = $self->param_decodeIf($params->{first_records_cond},{}); |
781
|
0
|
0
|
|
|
|
0
|
return undef unless (keys %$cond > 0); |
782
|
|
|
|
|
|
|
|
783
|
0
|
|
|
|
|
0
|
my $first_rows = [ $self->_chain_search_rs($Rs,$cond)->all ]; |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
#Hard coded munger for record_pk: |
786
|
0
|
|
|
|
|
0
|
foreach my $row (@$first_rows) { |
787
|
0
|
|
|
|
|
0
|
$row->{$self->record_pk} = $self->generate_record_pk_value($row); |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
# concat both sets of rows together, with first_rows first: |
791
|
0
|
|
|
|
|
0
|
push @$first_rows, @$rows; |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
# Remove duplicates: |
794
|
0
|
|
|
|
|
0
|
my %seen = (); |
795
|
0
|
|
|
|
|
0
|
@$first_rows = grep { !$seen{$_->{$self->record_pk}}++ } @$first_rows; |
|
0
|
|
|
|
|
0
|
|
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
# Shorten (truncate) to original length and replace original list with new list: |
798
|
0
|
|
|
|
|
0
|
@$rows = splice(@$first_rows, 0,@$rows); |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
sub rs_all { |
802
|
8
|
|
|
8
|
0
|
31
|
my ($self, $Rs) = @_; |
803
|
8
|
|
|
|
|
25
|
my $want = wantarray; |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
# ----- GitHub Issue #165 |
806
|
|
|
|
|
|
|
# NEW: extract the nested select refs from the special ''/-as structure for |
807
|
|
|
|
|
|
|
# the query, throwing away the outer layer and the -as. This is being done |
808
|
|
|
|
|
|
|
# for MSSQL specificially because this was causing 'AS' being added twice |
809
|
|
|
|
|
|
|
# in the generated query. We are now doing this here, after the fact, to |
810
|
|
|
|
|
|
|
# avoid having to refactor a lot of existing code which expects and looks for |
811
|
|
|
|
|
|
|
# these ''/-as structures (but this is a TODO to revisit). The only ramification |
812
|
|
|
|
|
|
|
# of stripping this structure appears to be in sorting; we can no longer sort |
813
|
|
|
|
|
|
|
# according to the '-as' name for virtual columns (see also the change further down |
814
|
|
|
|
|
|
|
# regarding sorting, also tagged as #165). So, istead we have to sort |
815
|
|
|
|
|
|
|
# on the select ref again. We already had to give up on using predeclared names |
816
|
|
|
|
|
|
|
# for HAVING because Pg didn't like them (#51), and it seems MSSQL doesn't like |
817
|
|
|
|
|
|
|
# it for sorting either. So we are falling back to the broadest compatability. |
818
|
|
|
|
|
|
|
# TODO: optimize cases for each different backend |
819
|
8
|
50
|
|
|
|
59
|
if(my $sels = $Rs->{attrs}{select}) { |
820
|
8
|
|
|
|
|
58
|
@$sels = map { $self->_extract_hash_inner_AS($_) } @$sels |
|
46
|
|
|
|
|
130
|
|
821
|
|
|
|
|
|
|
} |
822
|
|
|
|
|
|
|
# ----- |
823
|
|
|
|
|
|
|
|
824
|
8
|
|
|
|
|
31
|
my @ret = (); |
825
|
|
|
|
|
|
|
try { |
826
|
8
|
50
|
|
8
|
|
407
|
@ret = $want ? $Rs->all : scalar $Rs->all |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
catch { |
829
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
830
|
|
|
|
|
|
|
|
831
|
0
|
|
|
|
|
0
|
my $dbh = $Rs->result_source->schema->storage->dbh; |
832
|
0
|
|
0
|
|
|
0
|
my $LRL = $dbh->{LongReadLen} || 80; |
833
|
|
|
|
|
|
|
|
834
|
0
|
0
|
0
|
|
|
0
|
if($LRL == 80 && "$err" =~ /or LongReadLen too small/) { |
835
|
0
|
|
|
|
|
0
|
local $dbh->{LongReadLen} = 1024*256; |
836
|
0
|
|
|
|
|
0
|
warn join("\n",'','', |
837
|
|
|
|
|
|
|
' Caught DBI LongTruncOk/LongReadLen exception and LongReadLen not configured --', |
838
|
|
|
|
|
|
|
" Trying over with really large LongReadLen : $dbh->{LongReadLen}", |
839
|
|
|
|
|
|
|
' You need to set this to a real/appropriate value for your database','','' |
840
|
|
|
|
|
|
|
); |
841
|
0
|
0
|
|
|
|
0
|
@ret = $want ? $self->rs_all($Rs) : scalar $self->rs_all($Rs) |
842
|
|
|
|
|
|
|
} |
843
|
|
|
|
|
|
|
else { |
844
|
0
|
|
|
|
|
0
|
die $err |
845
|
|
|
|
|
|
|
} |
846
|
8
|
|
|
|
|
95
|
}; |
847
|
|
|
|
|
|
|
|
848
|
8
|
50
|
|
|
|
24177
|
$want ? @ret : $ret[0] |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
sub _is_special_AS_hash { |
853
|
46
|
|
|
46
|
|
82
|
my ($self, $h) = @_; |
854
|
46
|
50
|
100
|
|
|
414
|
(ref($h)||'') eq 'HASH' && exists $h->{''} && exists $h->{-as} && scalar(keys %$h) == 2 |
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
855
|
|
|
|
|
|
|
} |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
# extract the nested select ref from the special ''/-as structure |
858
|
|
|
|
|
|
|
sub _extract_hash_inner_AS { |
859
|
46
|
|
|
46
|
|
99
|
my ($self, $select) = @_; |
860
|
46
|
100
|
|
|
|
108
|
$self->_is_special_AS_hash($select) ? $select->{''} : $select |
861
|
|
|
|
|
|
|
} |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
has '_count_col', is => 'ro', lazy => 1, default => sub { |
865
|
|
|
|
|
|
|
my $self = shift; |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
# We only want to use this optimization for PostgreSQL, since it is |
868
|
|
|
|
|
|
|
# known to perform poorly with the standard count(*) method, which |
869
|
|
|
|
|
|
|
# is uniquely a Pg issue.... |
870
|
|
|
|
|
|
|
my $sqlt_type = $self->ResultSource->schema->storage->sqlt_type || ''; |
871
|
|
|
|
|
|
|
$sqlt_type eq 'PostgreSQL' or return undef; |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
my @pris = $self->ResultSource->primary_columns; |
874
|
|
|
|
|
|
|
if(scalar(@pris) == 1) { |
875
|
|
|
|
|
|
|
return $pris[0]; |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
return undef; |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
}, isa => 'Maybe[Str]'; |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
sub rs_count { |
882
|
8
|
|
|
8
|
0
|
218
|
my $self = shift; |
883
|
8
|
|
|
|
|
21
|
my $Rs2 = shift; |
884
|
8
|
|
50
|
|
|
35
|
my $params = shift || {}; |
885
|
|
|
|
|
|
|
|
886
|
8
|
50
|
|
|
|
312
|
return 1 if ($self->single_record_fetch); |
887
|
8
|
50
|
|
|
|
74
|
return undef if ($params->{no_total_count}); |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
# Optionally return the client supplied cached total: |
890
|
|
|
|
|
|
|
return $params->{cached_total_count} |
891
|
8
|
50
|
33
|
|
|
310
|
if($self->cache_total_count && exists $params->{cached_total_count}); |
892
|
|
|
|
|
|
|
|
893
|
8
|
|
|
|
|
65
|
$self->c->stash->{query_count_start} = [gettimeofday]; |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
#return $self->rs_count_manual($Rs2); |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
#return $self->rs_count_via_pager($Rs2); |
898
|
|
|
|
|
|
|
#return $self->rs_count_manual($Rs2); |
899
|
|
|
|
|
|
|
|
900
|
8
|
50
|
|
|
|
914
|
if(my $col = $self->_count_col) { |
901
|
|
|
|
|
|
|
return try { |
902
|
0
|
|
|
0
|
|
0
|
$Rs2->search_rs(undef,{ |
903
|
|
|
|
|
|
|
page => undef, rows => undef, order_by => undef, |
904
|
|
|
|
|
|
|
select => { count => join('.',$Rs2->current_source_alias,$col) }, |
905
|
|
|
|
|
|
|
as => 'count' |
906
|
|
|
|
|
|
|
}) |
907
|
|
|
|
|
|
|
->get_column('count') |
908
|
|
|
|
|
|
|
->first |
909
|
|
|
|
|
|
|
} |
910
|
|
|
|
|
|
|
catch { |
911
|
0
|
|
|
0
|
|
0
|
warn RED . "\n\n" . $self->extract_db_error_from_exception($_) . CLEAR; |
912
|
0
|
|
|
|
|
0
|
warn RED.BOLD . "\n\n" . |
913
|
|
|
|
|
|
|
'COUNT VIA _count_col FAILED, FAILING BACK TO PAGER COUNT' . |
914
|
|
|
|
|
|
|
"\n\n" . CLEAR; |
915
|
0
|
|
|
|
|
0
|
return $self->rs_count_with_fallbacks($Rs2); |
916
|
0
|
|
|
|
|
0
|
}; |
917
|
|
|
|
|
|
|
} |
918
|
|
|
|
|
|
|
|
919
|
8
|
|
|
|
|
94
|
return $self->rs_count_with_fallbacks($Rs2); |
920
|
|
|
|
|
|
|
} |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
sub rs_count_via_pager { |
923
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
924
|
0
|
|
|
|
|
0
|
my $Rs2 = shift; |
925
|
0
|
|
|
|
|
0
|
return $Rs2->pager->total_entries; |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
# -- Alternate way to calculate the total count. The logic in 'pager->total_entries' just |
929
|
|
|
|
|
|
|
# isn't entirely reliable still. Have been going back and forth between these two |
930
|
|
|
|
|
|
|
# approaches, this time, I am leaving both in as separates functions (after writing this |
931
|
|
|
|
|
|
|
# same code for the 3rd time at least!). The latest problem with the pager breaks with multiple |
932
|
|
|
|
|
|
|
# having conditions on the same virtual column. The DBIC pager/total_entries code is |
933
|
|
|
|
|
|
|
# putting in the same sub-select, with AS, for each condition into the select which throws a |
934
|
|
|
|
|
|
|
# duplicate column db exception (MySQL). |
935
|
|
|
|
|
|
|
# UPDATE: added options to fine-tune behaviors: |
936
|
|
|
|
|
|
|
sub rs_count_manual { |
937
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
938
|
0
|
|
|
|
|
0
|
my $Rs2 = shift; |
939
|
0
|
|
|
|
|
0
|
my %opts = @_; |
940
|
|
|
|
|
|
|
|
941
|
0
|
|
|
|
|
0
|
my $attr = { |
942
|
|
|
|
|
|
|
page => undef, |
943
|
|
|
|
|
|
|
rows => undef, |
944
|
|
|
|
|
|
|
order_by => undef |
945
|
|
|
|
|
|
|
}; |
946
|
|
|
|
|
|
|
|
947
|
0
|
0
|
|
|
|
0
|
unless($opts{no_strip_colums}) { |
948
|
0
|
|
|
|
|
0
|
my $cur_select = $Rs2->{attrs}->{select}; |
949
|
0
|
|
|
|
|
0
|
my $cur_as = $Rs2->{attrs}->{as}; |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
# strip all columns except virtual columns (show as hashrefs) |
952
|
0
|
|
|
|
|
0
|
my ($select,$as) = ([],[]); |
953
|
0
|
|
|
|
|
0
|
for my $i (0..$#$cur_select) { |
954
|
0
|
0
|
|
|
|
0
|
next unless (ref $cur_select->[$i]); |
955
|
0
|
|
|
|
|
0
|
push @$select, $cur_select->[$i]; |
956
|
0
|
|
|
|
|
0
|
push @$as, $cur_as->[$i]; |
957
|
|
|
|
|
|
|
} |
958
|
|
|
|
|
|
|
|
959
|
0
|
|
|
|
|
0
|
$attr = { %$attr, |
960
|
|
|
|
|
|
|
select => $select, |
961
|
|
|
|
|
|
|
as => $as |
962
|
|
|
|
|
|
|
}; |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
0
|
|
|
|
|
0
|
$Rs2 = $self->_chain_search_rs($Rs2,{},$attr); |
966
|
0
|
0
|
|
|
|
0
|
$Rs2 = $Rs2->as_subselect_rs unless ($opts{no_subselect}); |
967
|
|
|
|
|
|
|
|
968
|
0
|
0
|
|
|
|
0
|
return $Rs2->count_literal if ($opts{count_literal}); |
969
|
0
|
|
|
|
|
0
|
return $Rs2->count; |
970
|
|
|
|
|
|
|
} |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
# 3rd alternative for getting the rs_count, tries several methods. This is not currently |
973
|
|
|
|
|
|
|
# active, even though it is arguably the more reliable approach, because we don't want |
974
|
|
|
|
|
|
|
# to hide problems by stopping the app from breaking. This is here mostly for future |
975
|
|
|
|
|
|
|
# reference and for debugging |
976
|
|
|
|
|
|
|
sub rs_count_with_fallbacks { |
977
|
8
|
|
|
8
|
0
|
24
|
my $self = shift; |
978
|
8
|
|
|
|
|
21
|
my $Rs2 = shift; |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
return try { |
981
|
|
|
|
|
|
|
try { |
982
|
8
|
|
|
|
|
352
|
$Rs2->pager->total_entries |
983
|
|
|
|
|
|
|
} catch { |
984
|
0
|
|
|
|
|
0
|
warn RED . "\n\n" . $self->extract_db_error_from_exception($_) . CLEAR; |
985
|
0
|
|
|
|
|
0
|
warn RED.BOLD . "\n\n" . |
986
|
|
|
|
|
|
|
'COUNT VIA PAGER FAILED, FAILING BACK TO MANUAL COUNT' . |
987
|
|
|
|
|
|
|
"\n\n" . CLEAR; |
988
|
0
|
|
|
|
|
0
|
my $opt = {}; |
989
|
|
|
|
|
|
|
try { |
990
|
0
|
|
|
|
|
0
|
$self->rs_count_manual($Rs2,%$opt) |
991
|
|
|
|
|
|
|
} catch { |
992
|
0
|
|
|
|
|
0
|
$opt->{no_strip_colums} = 1; |
993
|
0
|
|
|
|
|
0
|
warn RED . "\n\n" . $self->extract_db_error_from_exception($_) . CLEAR; |
994
|
0
|
|
|
|
|
0
|
warn RED.BOLD . "\n\n" . |
995
|
|
|
|
|
|
|
'COUNT VIA MANUAL FAILED, TRYING AGAIN WITHOUT STRIPPING COLUMNS ' . Dumper($opt) . |
996
|
|
|
|
|
|
|
"\n" . CLEAR; |
997
|
|
|
|
|
|
|
try { |
998
|
0
|
|
|
|
|
0
|
$self->rs_count_manual($Rs2,%$opt) |
999
|
|
|
|
|
|
|
} catch { |
1000
|
0
|
|
|
|
|
0
|
$opt->{count_literal} = 1; |
1001
|
0
|
|
|
|
|
0
|
warn RED . "\n\n" . $self->extract_db_error_from_exception($_) . CLEAR; |
1002
|
0
|
|
|
|
|
0
|
warn RED.BOLD . "\n\n" . |
1003
|
|
|
|
|
|
|
'COUNT VIA MANUAL FAILED, TRYING AGAIN WITH COUNT_LITERAL ' . Dumper($opt) . |
1004
|
|
|
|
|
|
|
"\n" . CLEAR; |
1005
|
0
|
|
|
|
|
0
|
$self->rs_count_manual($Rs2,%$opt) |
1006
|
|
|
|
|
|
|
} |
1007
|
0
|
|
|
|
|
0
|
}; |
|
0
|
|
|
|
|
0
|
|
1008
|
8
|
|
|
8
|
|
435
|
}; |
1009
|
|
|
|
|
|
|
} catch { |
1010
|
0
|
|
|
0
|
|
0
|
warn RED . "\n\n" . $self->extract_db_error_from_exception($_) . CLEAR; |
1011
|
0
|
|
|
|
|
0
|
warn RED.BOLD . "\n\n" . |
1012
|
|
|
|
|
|
|
'FAILED TO GET TOTAL COUNT, GIVING UP' . |
1013
|
|
|
|
|
|
|
"\n\n" . CLEAR; |
1014
|
0
|
|
|
|
|
0
|
die $_; |
1015
|
8
|
|
|
|
|
98
|
}; |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
# -- |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
after rs_count => sub { |
1021
|
|
|
|
|
|
|
my $self = shift; |
1022
|
|
|
|
|
|
|
my $start = $self->c->stash->{query_count_start} || return undef; |
1023
|
|
|
|
|
|
|
my $elapsed = tv_interval($start); |
1024
|
|
|
|
|
|
|
$self->c->stash->{query_count_time} = sprintf('%.2f',$elapsed) . 's'; |
1025
|
|
|
|
|
|
|
}; |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
sub query_time { |
1029
|
8
|
|
|
8
|
0
|
42
|
my $self = shift; |
1030
|
8
|
|
50
|
|
|
32
|
my $qt = $self->c->stash->{query_time} || return undef; |
1031
|
8
|
50
|
|
|
|
597
|
$qt .= '/' . $self->c->stash->{query_count_time} if ($self->c->stash->{query_count_time}); |
1032
|
8
|
|
|
|
|
535
|
return $qt; |
1033
|
|
|
|
|
|
|
} |
1034
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
has '_dbh_driver', is => 'ro', lazy => 1, default => sub { |
1037
|
|
|
|
|
|
|
my $self = shift; |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
my $dbh = $self->ResultSource->schema->storage->dbh; |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
my $driver = $dbh->{Driver}->{Name}; |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
if($driver eq 'ODBC') { |
1044
|
|
|
|
|
|
|
my $dbms_name = $dbh->get_info($GetInfoType{SQL_DBMS_NAME}); |
1045
|
|
|
|
|
|
|
$driver = 'MSSQL' if ($dbms_name eq 'Microsoft SQL Server'); |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
return $driver |
1049
|
|
|
|
|
|
|
}, isa => 'Str'; |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
has '_named_column_summaries', is => 'ro', lazy => 1, default => sub { |
1052
|
|
|
|
|
|
|
my $self = shift; |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
my $d = { |
1055
|
|
|
|
|
|
|
sum => 'sum', |
1056
|
|
|
|
|
|
|
max => 'max', |
1057
|
|
|
|
|
|
|
min => 'min', |
1058
|
|
|
|
|
|
|
count => 'count', |
1059
|
|
|
|
|
|
|
count_uniq => 'count(distinct({x}))', |
1060
|
|
|
|
|
|
|
avg => 'round(avg({x}),2)', |
1061
|
|
|
|
|
|
|
}; |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
if($self->_dbh_driver eq 'mysql') { |
1064
|
|
|
|
|
|
|
%$d = ( %$d, |
1065
|
|
|
|
|
|
|
oldest_days => 'CONCAT(DATEDIFF(NOW(),min({x})),\' days\')', |
1066
|
|
|
|
|
|
|
youngest_days => 'CONCAT(DATEDIFF(NOW(),max({x})),\' days\')', |
1067
|
|
|
|
|
|
|
age_range_days => 'CONCAT(DATEDIFF(max({x}),min({x})),\' days\')', |
1068
|
|
|
|
|
|
|
); |
1069
|
|
|
|
|
|
|
} |
1070
|
|
|
|
|
|
|
elsif($self->_dbh_driver eq 'SQLite') { |
1071
|
|
|
|
|
|
|
# TODO ... |
1072
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
} |
1074
|
|
|
|
|
|
|
elsif($self->_dbh_driver eq 'Pg') { |
1075
|
|
|
|
|
|
|
# TODO ... |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
} |
1078
|
|
|
|
|
|
|
elsif($self->_dbh_driver eq 'MSSQL') { |
1079
|
|
|
|
|
|
|
# TODO ... |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
return $d |
1084
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
}, isa => 'HashRef'; |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
sub calculate_column_summaries { |
1088
|
8
|
|
|
8
|
0
|
32
|
my ($self,$ret,$Rs,$params) = @_; |
1089
|
8
|
50
|
33
|
|
|
78
|
return unless ($params && $params->{column_summaries}); |
1090
|
|
|
|
|
|
|
|
1091
|
0
|
|
|
|
|
0
|
my $sums = $self->param_decodeIf($params->{column_summaries},{}); |
1092
|
0
|
0
|
|
|
|
0
|
return unless (keys %$sums > 0); |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
# -- Filter out summaries for cols that weren't requested in 'columns': |
1095
|
0
|
|
|
|
|
0
|
my $req_cols = $self->c->stash->{req_columns}; #<-- previously calculated in get_req_columns(): |
1096
|
0
|
0
|
0
|
|
|
0
|
if($req_cols && @$req_cols > 0) { |
1097
|
0
|
|
|
|
|
0
|
my %limit = map {$_=>1} @$req_cols; |
|
0
|
|
|
|
|
0
|
|
1098
|
0
|
|
|
|
|
0
|
%$sums = map {$_=>$sums->{$_}} grep {$limit{$_}} keys %$sums; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1099
|
|
|
|
|
|
|
} |
1100
|
|
|
|
|
|
|
# -- |
1101
|
|
|
|
|
|
|
|
1102
|
0
|
|
|
|
|
0
|
my $select = []; |
1103
|
0
|
|
|
|
|
0
|
my $as = []; |
1104
|
|
|
|
|
|
|
|
1105
|
0
|
|
|
|
|
0
|
my %extra = (); |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
#foreach my $col (@{$Rs->{attrs}->{as}}) { |
1108
|
0
|
|
|
|
|
0
|
foreach my $col (keys %$sums) { |
1109
|
0
|
|
|
|
|
0
|
my $sum = $sums->{$col}; |
1110
|
0
|
0
|
|
|
|
0
|
if($sum) { |
1111
|
0
|
|
|
|
|
0
|
my $dbic_name = $self->resolve_dbic_render_colname($col); |
1112
|
0
|
|
|
|
|
0
|
local $self->{_get_col_summary_select_msg} = undef; |
1113
|
0
|
|
|
|
|
0
|
my $sel = $self->get_col_summary_select($dbic_name,$sum); |
1114
|
0
|
0
|
|
|
|
0
|
if($sel) { |
1115
|
0
|
|
|
|
|
0
|
push @$select, $sel; |
1116
|
0
|
|
|
|
|
0
|
push @$as, $col; |
1117
|
|
|
|
|
|
|
} |
1118
|
|
|
|
|
|
|
else { |
1119
|
0
|
|
0
|
|
|
0
|
$extra{$col} = $self->{_get_col_summary_select_msg} || 'BadFunc!'; |
1120
|
|
|
|
|
|
|
} |
1121
|
|
|
|
|
|
|
} |
1122
|
|
|
|
|
|
|
} |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
try { |
1125
|
0
|
0
|
|
0
|
|
0
|
if(scalar(@$select) > 0) { |
1126
|
0
|
0
|
|
|
|
0
|
my $agg = $self->_chain_search_rs($Rs,undef,{ |
1127
|
|
|
|
|
|
|
rows => 1, page => undef, order_by => undef, |
1128
|
|
|
|
|
|
|
select => $select, as => $as, |
1129
|
|
|
|
|
|
|
result_class => 'DBIx::Class::ResultClass::HashRefInflator' |
1130
|
|
|
|
|
|
|
})->first or return; |
1131
|
|
|
|
|
|
|
|
1132
|
0
|
|
|
|
|
0
|
$ret->{column_summaries} = { %$agg, %extra }; |
1133
|
|
|
|
|
|
|
} |
1134
|
|
|
|
|
|
|
else { |
1135
|
0
|
|
|
|
|
0
|
$ret->{column_summaries} = \%extra; |
1136
|
|
|
|
|
|
|
} |
1137
|
|
|
|
|
|
|
} |
1138
|
|
|
|
|
|
|
catch { |
1139
|
0
|
|
|
0
|
|
0
|
$self->c->log->error("$_"); |
1140
|
0
|
|
|
|
|
0
|
$ret->{column_summaries} = { map {$_=>'FuncError!'} keys %$sums }; |
|
0
|
|
|
|
|
0
|
|
1141
|
0
|
|
|
|
|
0
|
}; |
1142
|
|
|
|
|
|
|
}; |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
sub get_col_summary_select { |
1145
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1146
|
0
|
|
|
|
|
0
|
my $col = shift; |
1147
|
0
|
|
|
|
|
0
|
my $func = shift; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
# Lookup by predefined name if starts with '&' |
1150
|
0
|
0
|
|
|
|
0
|
if($func =~ /^\&(.+)$/) { |
1151
|
0
|
|
|
|
|
0
|
my $name = $1; |
1152
|
0
|
|
|
|
|
0
|
$func = $self->_named_column_summaries->{$name}; |
1153
|
0
|
0
|
|
|
|
0
|
unless($func) { |
1154
|
0
|
0
|
|
|
|
0
|
$self->{_get_col_summary_select_msg} = 'Unsupported' if(exists $self->{_get_col_summary_select_msg}); |
1155
|
0
|
|
|
|
|
0
|
return undef; |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
} |
1158
|
|
|
|
|
|
|
else { |
1159
|
|
|
|
|
|
|
# TODO: check to enforce allow_custom_summary_functions is true or die |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
} |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
# --- |
1164
|
|
|
|
|
|
|
# NEW: Look for and extract the SQL literal from the special ''/-as structure |
1165
|
|
|
|
|
|
|
# which is how virtual columns appear. This is the structure needed for the |
1166
|
|
|
|
|
|
|
# select, we need to strip this wrapper out for use here |
1167
|
|
|
|
|
|
|
$col = $col->{''} if ( |
1168
|
|
|
|
|
|
|
ref($col) && ref($col) eq 'HASH' |
1169
|
|
|
|
|
|
|
&& scalar(keys %$col) == 2 |
1170
|
0
|
0
|
0
|
|
|
0
|
&& $col->{''} && $col->{'-as'} |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
1171
|
|
|
|
|
|
|
); |
1172
|
|
|
|
|
|
|
# Normalize to a ScalarRef from several known nested structures (multi-rel vs virtual) |
1173
|
0
|
0
|
|
|
|
0
|
if(ref($col)) { |
1174
|
0
|
0
|
|
|
|
0
|
$col = $$col if (ref($col) eq 'REF'); |
1175
|
0
|
0
|
|
|
|
0
|
$col = $col->[0] if (ref($col) eq 'ARRAY'); |
1176
|
0
|
0
|
|
|
|
0
|
$col = \"$col" unless (ref $col); |
1177
|
|
|
|
|
|
|
} |
1178
|
|
|
|
|
|
|
# --- |
1179
|
|
|
|
|
|
|
|
1180
|
0
|
|
|
|
|
0
|
$func =~ s/^\s+//; |
1181
|
0
|
|
|
|
|
0
|
$func =~ s/\s+$//; |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
# Simple function name |
1184
|
0
|
0
|
|
|
|
0
|
return { uc($func) => $col } if ($func =~ /^[a-zA-Z]+$/); |
1185
|
|
|
|
|
|
|
|
1186
|
0
|
0
|
0
|
|
|
0
|
$col = $$col if (ref($col) && ref($col) eq 'SCALAR'); |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
# Replace macro string '{x}' with the column name |
1189
|
0
|
|
|
|
|
0
|
$func =~ s/\{x\}/${col}/g; |
1190
|
|
|
|
|
|
|
|
1191
|
0
|
|
|
|
|
0
|
return \[ $func ]; |
1192
|
|
|
|
|
|
|
|
1193
|
0
|
|
|
|
|
0
|
return undef; |
1194
|
|
|
|
|
|
|
} |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
# Applies base request attrs to ResultSet: |
1199
|
|
|
|
|
|
|
sub chain_Rs_req_base_Attr { |
1200
|
8
|
|
|
8
|
0
|
27
|
my $self = shift; |
1201
|
8
|
|
33
|
|
|
58
|
my $Rs = shift || $self->_ResultSet; |
1202
|
8
|
|
33
|
|
|
39
|
my $params = shift || $self->c->req->params; |
1203
|
|
|
|
|
|
|
|
1204
|
8
|
|
|
|
|
113
|
$params = { |
1205
|
|
|
|
|
|
|
start => 0, |
1206
|
|
|
|
|
|
|
limit => 10000000, |
1207
|
|
|
|
|
|
|
dir => 'asc', |
1208
|
|
|
|
|
|
|
%$params |
1209
|
|
|
|
|
|
|
}; |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
my $attr = { |
1212
|
|
|
|
|
|
|
'select' => [], |
1213
|
|
|
|
|
|
|
'as' => [], |
1214
|
|
|
|
|
|
|
join => {}, |
1215
|
|
|
|
|
|
|
page => int($params->{start}/$params->{limit}) + 1, |
1216
|
|
|
|
|
|
|
rows => $params->{limit} |
1217
|
8
|
|
|
|
|
108
|
}; |
1218
|
|
|
|
|
|
|
|
1219
|
8
|
|
|
|
|
60
|
my $columns = $self->get_req_columns; |
1220
|
|
|
|
|
|
|
|
1221
|
8
|
|
|
|
|
24
|
my $used_aliases = {}; |
1222
|
|
|
|
|
|
|
|
1223
|
8
|
|
|
|
|
26
|
for my $col (@$columns) { |
1224
|
46
|
|
|
|
|
185
|
my $dbic_name = $self->resolve_dbic_colname($col,$attr->{join}); |
1225
|
|
|
|
|
|
|
|
1226
|
46
|
100
|
|
|
|
51207
|
unless (ref $dbic_name) { |
1227
|
17
|
|
|
|
|
87
|
my ($alias,$field) = split(/\./,$dbic_name); |
1228
|
17
|
|
|
|
|
94
|
my $prefix = $col; |
1229
|
|
|
|
|
|
|
|
1230
|
17
|
|
|
|
|
355
|
$prefix =~ s/${field}$//; |
1231
|
17
|
100
|
|
|
|
95
|
$used_aliases->{$alias} = {} unless ($used_aliases->{$alias}); |
1232
|
17
|
50
|
|
|
|
57
|
$used_aliases->{$alias}->{$prefix}++ unless($alias eq 'me'); |
1233
|
17
|
|
|
|
|
36
|
my $count = scalar(keys %{$used_aliases->{$alias}}); |
|
17
|
|
|
|
|
52
|
|
1234
|
|
|
|
|
|
|
# automatically set alias for duplicate joins: |
1235
|
17
|
50
|
|
|
|
90
|
$dbic_name = $alias . '_' . $count . '.' . $field if($count > 1); |
1236
|
|
|
|
|
|
|
} |
1237
|
|
|
|
|
|
|
|
1238
|
46
|
|
|
|
|
93
|
push @{$attr->{'select'}}, $dbic_name; |
|
46
|
|
|
|
|
149
|
|
1239
|
46
|
|
|
|
|
84
|
push @{$attr->{'as'}}, $col; |
|
46
|
|
|
|
|
145
|
|
1240
|
|
|
|
|
|
|
} |
1241
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
my @sorts = defined $params->{sorters} |
1243
|
0
|
|
|
|
|
0
|
? @{$self->param_decodeIf($params->{sorters},[])} |
1244
|
|
|
|
|
|
|
: $params->{sort} |
1245
|
|
|
|
|
|
|
? ({ field => $params->{sort}, direction => ( |
1246
|
8
|
0
|
|
|
|
66
|
$params->{dir} eq 'DESC' ? 'DESC' : 'ASC' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
)}) |
1248
|
|
|
|
|
|
|
: (); |
1249
|
|
|
|
|
|
|
|
1250
|
8
|
|
|
|
|
32
|
for my $sort (@sorts) { |
1251
|
0
|
|
|
|
|
0
|
my $field = $sort->{field}; |
1252
|
0
|
|
|
|
|
0
|
my $sort_name = $self->resolve_dbic_render_colname($field,$attr->{join}); |
1253
|
|
|
|
|
|
|
|
1254
|
|
|
|
|
|
|
# ----- GitHub Issue #165 |
1255
|
|
|
|
|
|
|
# (See also comments tagged with #165 in rs_all further up...) |
1256
|
0
|
0
|
|
|
|
0
|
$sort_name = ref($sort_name) ? $self->_extract_virtual_subselect_ref($sort_name) : $sort_name; |
1257
|
|
|
|
|
|
|
# we can no longer use the '-as' name for sorting with virtual columns because |
1258
|
|
|
|
|
|
|
# MSSQL doesn't like it. So we're just using the actual select/ref again, which |
1259
|
|
|
|
|
|
|
# is probably slower, but works the same across different backends. |
1260
|
|
|
|
|
|
|
#if (ref $sort_name eq 'HASH') { |
1261
|
|
|
|
|
|
|
# die "Can't sort by column if it doesn't have an SQL alias" |
1262
|
|
|
|
|
|
|
# unless exists $sort_name->{-as}; |
1263
|
|
|
|
|
|
|
# $sort_name= $sort_name->{-as}; |
1264
|
|
|
|
|
|
|
#} |
1265
|
|
|
|
|
|
|
# ----- |
1266
|
|
|
|
|
|
|
my @order_by = ref $attr->{order_by} eq 'HASH' |
1267
|
|
|
|
|
|
|
? ($attr->{order_by}) |
1268
|
|
|
|
|
|
|
: ref $attr->{order_by} eq 'ARRAY' |
1269
|
0
|
0
|
|
|
|
0
|
? @{$attr->{order_by}} |
|
0
|
0
|
|
|
|
0
|
|
1270
|
|
|
|
|
|
|
: (); |
1271
|
0
|
|
|
|
|
0
|
push @order_by, { '-' . lc($sort->{direction}) => $sort_name }; |
1272
|
0
|
|
|
|
|
0
|
$attr->{order_by} = \@order_by; |
1273
|
|
|
|
|
|
|
} |
1274
|
|
|
|
|
|
|
|
1275
|
8
|
|
|
|
|
89
|
return $self->_chain_search_rs($Rs,{},$attr); |
1276
|
|
|
|
|
|
|
} |
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
sub resolve_dbic_colname { |
1279
|
46
|
|
|
46
|
0
|
89
|
my $self = shift; |
1280
|
46
|
|
|
|
|
1685
|
return $self->TableSpec->resolve_dbic_colname(@_); |
1281
|
|
|
|
|
|
|
} |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
sub resolve_dbic_render_colname { |
1285
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1286
|
0
|
|
|
|
|
0
|
my $name = shift; |
1287
|
0
|
|
0
|
|
|
0
|
my $join = shift || {}; |
1288
|
|
|
|
|
|
|
|
1289
|
0
|
|
0
|
|
|
0
|
$self->c->stash->{dbic_render_colnames} = $self->c->stash->{dbic_render_colnames} || {}; |
1290
|
0
|
|
|
|
|
0
|
my $h = $self->c->stash->{dbic_render_colnames}; |
1291
|
|
|
|
|
|
|
|
1292
|
0
|
|
|
|
|
0
|
my $get_render_col = 1; |
1293
|
0
|
|
0
|
|
|
0
|
$h->{$name} = $h->{$name} || $self->resolve_dbic_colname($name,$join,$get_render_col); |
1294
|
|
|
|
|
|
|
|
1295
|
0
|
|
|
|
|
0
|
return $h->{$name}; |
1296
|
|
|
|
|
|
|
} |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
has 'always_fetch_columns', is => 'ro', lazy => 1, default => sub { |
1299
|
|
|
|
|
|
|
my $self = shift; |
1300
|
|
|
|
|
|
|
return [] unless ($self->always_fetch_colspec); |
1301
|
|
|
|
|
|
|
return [ $self->TableSpec->get_colspec_column_names( |
1302
|
|
|
|
|
|
|
$self->TableSpec->always_fetch_colspec->colspecs |
1303
|
|
|
|
|
|
|
)]; |
1304
|
|
|
|
|
|
|
}, isa => 'ArrayRef'; |
1305
|
8
|
|
|
8
|
0
|
24
|
sub all_always_fetch_columns { uniq( @{(shift)->always_fetch_columns} ) } |
|
8
|
|
|
|
|
299
|
|
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
sub get_req_columns { |
1308
|
8
|
|
|
8
|
0
|
24
|
my $self = shift; |
1309
|
8
|
|
33
|
|
|
66
|
my $params = shift || $self->c->req->params; |
1310
|
8
|
|
50
|
|
|
786
|
my $param_name = shift || 'columns'; |
1311
|
|
|
|
|
|
|
|
1312
|
8
|
|
|
|
|
22
|
my $columns = $params; |
1313
|
8
|
50
|
|
|
|
84
|
$columns = $self->param_decodeIf($params->{$param_name},[]) if (ref($params) eq 'HASH'); |
1314
|
|
|
|
|
|
|
|
1315
|
8
|
50
|
|
|
|
51
|
die "get_req_columns(): bad options" unless(ref($columns) eq 'ARRAY'); |
1316
|
|
|
|
|
|
|
|
1317
|
8
|
|
|
|
|
58
|
$self->c->stash->{req_columns} = [@$columns]; |
1318
|
|
|
|
|
|
|
|
1319
|
|
|
|
|
|
|
# --- |
1320
|
|
|
|
|
|
|
# If no columns were supplied by the client, add all the columns from |
1321
|
|
|
|
|
|
|
# include_relspec |
1322
|
|
|
|
|
|
|
# TODO: move column request logic that's currently only in AppGrid2 to a |
1323
|
|
|
|
|
|
|
# plugin/store where it can be used by other js modules like dataview |
1324
|
8
|
50
|
|
|
|
738
|
unless(@$columns > 0) { |
1325
|
|
|
|
|
|
|
# new/simple way: |
1326
|
0
|
|
|
|
|
0
|
@$columns = grep { $_ ne $self->record_pk } $self->column_name_list; |
|
0
|
|
|
|
|
0
|
|
1327
|
|
|
|
|
|
|
# old, more complex (and slow) approach: |
1328
|
|
|
|
|
|
|
#push @$columns, $self->TableSpec->get_colspec_column_names( |
1329
|
|
|
|
|
|
|
# $self->TableSpec->include_colspec->colspecs |
1330
|
|
|
|
|
|
|
#); |
1331
|
|
|
|
|
|
|
## Limit to current real/valid columns according to DataStore2: |
1332
|
|
|
|
|
|
|
#my %cols_indx = map {$_=>1} $self->column_name_list; |
1333
|
|
|
|
|
|
|
#@$columns = grep { $cols_indx{$_} } @$columns; |
1334
|
|
|
|
|
|
|
} |
1335
|
|
|
|
|
|
|
# --- |
1336
|
|
|
|
|
|
|
|
1337
|
8
|
|
|
|
|
66
|
push @$columns, $self->all_always_fetch_columns; |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
# Make sure the supplied sort column is included (needed for sorting on a *hidden* virtual |
1340
|
|
|
|
|
|
|
# columns, including mutli and m2m relationship columns) |
1341
|
8
|
50
|
|
|
|
44
|
push @$columns, $params->{sort} if ($params->{sort}); |
1342
|
|
|
|
|
|
|
|
1343
|
8
|
|
|
|
|
352
|
my @exclude = ( $self->record_pk, 'loadContentCnf' ); |
1344
|
|
|
|
|
|
|
|
1345
|
8
|
|
|
|
|
28
|
push @$columns, @{$self->primary_columns}; |
|
8
|
|
|
|
|
288
|
|
1346
|
|
|
|
|
|
|
|
1347
|
8
|
|
|
|
|
25
|
my @req_fetch = (); |
1348
|
8
|
|
|
|
|
31
|
foreach my $col (grep {defined $self->columns->{$_}} @$columns) { |
|
54
|
|
|
|
|
211
|
|
1349
|
54
|
50
|
|
|
|
196
|
my $req = $self->columns->{$col}->required_fetch_columns or next; |
1350
|
54
|
|
|
|
|
154
|
push @req_fetch, grep { defined $self->columns->{$_} } @$req; |
|
22
|
|
|
|
|
97
|
|
1351
|
|
|
|
|
|
|
} |
1352
|
8
|
|
|
|
|
68
|
push @$columns, @req_fetch; |
1353
|
|
|
|
|
|
|
|
1354
|
8
|
|
|
|
|
30
|
foreach my $col (@$columns) { |
1355
|
76
|
|
|
|
|
212
|
my $column = $self->columns->{$col}; |
1356
|
76
|
50
|
|
|
|
234
|
push @exclude, $col if ($column->{no_fetch}); |
1357
|
|
|
|
|
|
|
} |
1358
|
|
|
|
|
|
|
|
1359
|
8
|
|
|
|
|
44
|
uniq($columns); |
1360
|
8
|
|
|
|
|
24
|
my %excl = map { $_ => 1 } @exclude; |
|
16
|
|
|
|
|
62
|
|
1361
|
8
|
|
|
|
|
23
|
@$columns = grep { !$excl{$_} } @$columns; |
|
46
|
|
|
|
|
117
|
|
1362
|
|
|
|
|
|
|
|
1363
|
8
|
|
|
|
|
27
|
return $columns; |
1364
|
|
|
|
|
|
|
} |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
# Applies id_in filter to ResultSet: |
1368
|
|
|
|
|
|
|
sub chain_Rs_req_id_in { |
1369
|
8
|
|
|
8
|
0
|
25
|
my $self = shift; |
1370
|
8
|
|
33
|
|
|
820
|
my $Rs = shift || $self->_ResultSet; |
1371
|
8
|
|
33
|
|
|
35
|
my $params = shift || $self->c->req->params; |
1372
|
|
|
|
|
|
|
|
1373
|
8
|
100
|
|
|
|
62
|
my $id_in = $self->param_decodeIf($params->{id_in}) or return $Rs; |
1374
|
|
|
|
|
|
|
|
1375
|
3
|
50
|
33
|
|
|
27
|
return $Rs if (ref $id_in and ! ref($id_in) eq 'ARRAY'); |
1376
|
3
|
50
|
|
|
|
16
|
$id_in = [ $id_in ] unless (ref $id_in); |
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
# TODO: second form below doesn't work, find out why... |
1379
|
3
|
|
|
|
|
13
|
return $self->_chain_search_rs($Rs,{ '-or' => [ map { $self->record_pk_cond($_) } @$id_in ] }); |
|
3
|
|
|
|
|
22
|
|
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
## If there is more than one primary column, we have to construct the condition completely |
1382
|
|
|
|
|
|
|
## different: |
1383
|
|
|
|
|
|
|
#return $self->_chain_search_rs($Rs,{ '-or' => [ map { $self->record_pk_cond($_) } @$id_in ] }) |
1384
|
|
|
|
|
|
|
# if (@{$self->primary_columns} > 1); |
1385
|
|
|
|
|
|
|
# |
1386
|
|
|
|
|
|
|
## If there is really only one primary column we can use '-in' : |
1387
|
|
|
|
|
|
|
#my $col = $self->TableSpec->resolve_dbic_colname($self->primary_columns->[0]); |
1388
|
|
|
|
|
|
|
#return $self->_chain_search_rs($Rs,{ $col => { '-in' => $id_in } }); |
1389
|
|
|
|
|
|
|
} |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
# Applies additional explicit resultset cond/attr to ResultSet: |
1393
|
|
|
|
|
|
|
sub chain_Rs_req_explicit_resultset { |
1394
|
8
|
|
|
8
|
0
|
25
|
my $self = shift; |
1395
|
8
|
|
33
|
|
|
46
|
my $Rs = shift || $self->_ResultSet; |
1396
|
8
|
|
33
|
|
|
37
|
my $params = shift || $self->c->req->params; |
1397
|
|
|
|
|
|
|
|
1398
|
8
|
|
|
|
|
41
|
my $cond = $self->param_decodeIf($params->{resultset_condition},{}); |
1399
|
8
|
|
|
|
|
40
|
my $attr = $self->param_decodeIf($params->{resultset_attr},{}); |
1400
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
## |
1403
|
|
|
|
|
|
|
## TODO: make this code handle more cases |
1404
|
|
|
|
|
|
|
## This code converts [[ 'foo' ]] into \[ 'foo' ] and is needed because the later cannot |
1405
|
|
|
|
|
|
|
## be expressed in JSON. This allows the client to send a literal col name |
1406
|
8
|
50
|
|
|
|
41
|
if(ref($attr->{select}) eq 'ARRAY') { |
1407
|
0
|
|
|
|
|
0
|
my @new; |
1408
|
0
|
|
|
|
|
0
|
foreach my $sel (@{$attr->{select}}) { |
|
0
|
|
|
|
|
0
|
|
1409
|
0
|
0
|
0
|
|
|
0
|
if(ref($sel) eq 'ARRAY' and scalar @$sel == 1 and ref($sel->[0]) eq 'ARRAY') { |
|
|
|
0
|
|
|
|
|
1410
|
0
|
|
|
|
|
0
|
push @new, \[ $sel->[0]->[0] ]; |
1411
|
|
|
|
|
|
|
} |
1412
|
|
|
|
|
|
|
else { |
1413
|
0
|
|
|
|
|
0
|
push @new,$sel; |
1414
|
|
|
|
|
|
|
} |
1415
|
|
|
|
|
|
|
} |
1416
|
0
|
|
|
|
|
0
|
@{$attr->{select}} = @new; |
|
0
|
|
|
|
|
0
|
|
1417
|
|
|
|
|
|
|
} |
1418
|
|
|
|
|
|
|
## |
1419
|
|
|
|
|
|
|
## |
1420
|
|
|
|
|
|
|
|
1421
|
8
|
|
|
|
|
34
|
return $self->_chain_search_rs($Rs,$cond,$attr); |
1422
|
|
|
|
|
|
|
} |
1423
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
# Applies Quick Search to ResultSet: |
1426
|
|
|
|
|
|
|
sub chain_Rs_req_quicksearch { |
1427
|
8
|
|
|
8
|
0
|
28
|
my $self = shift; |
1428
|
8
|
|
33
|
|
|
52
|
my $Rs = shift || $self->_ResultSet; |
1429
|
8
|
|
33
|
|
|
50
|
my $params = shift || $self->c->req->params; |
1430
|
|
|
|
|
|
|
|
1431
|
8
|
50
|
33
|
|
|
64
|
delete $params->{qs_query} if (defined $params->{qs_query} and $params->{qs_query} eq ''); |
1432
|
8
|
50
|
|
|
|
50
|
my $query = $params->{qs_query} or return $Rs; |
1433
|
|
|
|
|
|
|
|
1434
|
0
|
|
|
|
|
0
|
my $fields = $self->param_decodeIf($params->{qs_fields},[]); |
1435
|
0
|
0
|
|
|
|
0
|
return $Rs unless (@$fields > 0); |
1436
|
|
|
|
|
|
|
|
1437
|
0
|
|
|
|
|
0
|
my $attr = { join => {} }; |
1438
|
|
|
|
|
|
|
|
1439
|
0
|
|
0
|
|
|
0
|
my $mode = $params->{quicksearch_mode} || $self->quicksearch_mode; |
1440
|
0
|
0
|
|
|
|
0
|
$mode = $self->quicksearch_mode unless ($self->allow_set_quicksearch_mode); |
1441
|
|
|
|
|
|
|
|
1442
|
0
|
|
|
|
|
0
|
my @search = (); |
1443
|
0
|
|
|
|
|
0
|
foreach my $field (@$fields) { |
1444
|
|
|
|
|
|
|
my $cond = $self->_resolve_quicksearch_condition( |
1445
|
|
|
|
|
|
|
$field, $query, { mode => $mode, joinref => $attr->{join} } |
1446
|
0
|
0
|
|
|
|
0
|
) or next; #<-- skip for undef (see below) |
1447
|
0
|
|
|
|
|
0
|
push @search, $cond; |
1448
|
|
|
|
|
|
|
} |
1449
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
# If no search conditions have been populated at all it means the query |
1451
|
|
|
|
|
|
|
# failed pre-validation for all active columns. We need to simulate |
1452
|
|
|
|
|
|
|
# a condition which will return no rows |
1453
|
0
|
0
|
|
|
|
0
|
unless(scalar(@search) > 0) { |
1454
|
|
|
|
|
|
|
# Simple dummy condition that will always be false to force 0 results |
1455
|
0
|
|
|
|
|
0
|
return $Rs->search_rs(\'1 = 2'); |
1456
|
|
|
|
|
|
|
} |
1457
|
|
|
|
|
|
|
|
1458
|
0
|
|
|
|
|
0
|
return $self->_chain_search_rs($Rs,{ '-or' => \@search },$attr); |
1459
|
|
|
|
|
|
|
} |
1460
|
|
|
|
|
|
|
|
1461
|
|
|
|
|
|
|
|
1462
|
|
|
|
|
|
|
sub _resolve_quicksearch_condition { |
1463
|
0
|
|
|
0
|
|
0
|
my ($self, $field, $query, $opt) = @_; |
1464
|
|
|
|
|
|
|
|
1465
|
0
|
0
|
|
|
|
0
|
my $cnf = $self->get_column($field) or die "field/column '$field' not found!"; |
1466
|
0
|
0
|
|
|
|
0
|
my $join = $opt->{joinref} or die "missing opt/ref 'joinref'"; |
1467
|
0
|
0
|
|
|
|
0
|
my $mode = $opt->{mode} or die "missing opt 'mode'"; |
1468
|
|
|
|
|
|
|
|
1469
|
|
|
|
|
|
|
# Force to exact mode via optional TableSpec column cnf override: |
1470
|
|
|
|
|
|
|
$mode = 'exact' if ( |
1471
|
|
|
|
|
|
|
exists $cnf->{quick_search_exact_only} |
1472
|
|
|
|
|
|
|
&& jstrue($cnf->{quick_search_exact_only}) |
1473
|
0
|
0
|
0
|
|
|
0
|
); |
1474
|
|
|
|
|
|
|
|
1475
|
0
|
|
0
|
|
|
0
|
my $dtype = $cnf->{broad_data_type} || 'text'; |
1476
|
0
|
|
|
|
|
0
|
my $dbicname = $self->_extract_hash_inner_AS( $self->resolve_dbic_colname($field,$join) ); |
1477
|
|
|
|
|
|
|
|
1478
|
|
|
|
|
|
|
# For numbers, force to 'exact' mode and discard (return undef) for queries |
1479
|
|
|
|
|
|
|
# which are not numbers (since we already know they will not match anything). |
1480
|
|
|
|
|
|
|
# This is also now safe for PostgreSQL which complains when you try to search |
1481
|
|
|
|
|
|
|
# on a numeric column with a non-numeric value: |
1482
|
0
|
0
|
|
|
|
0
|
if ($dtype eq 'integer') { |
|
|
0
|
|
|
|
|
|
1483
|
0
|
0
|
|
|
|
0
|
return undef unless $query =~ /^[+-]*[0-9]+$/; |
1484
|
0
|
|
|
|
|
0
|
$mode = 'exact'; |
1485
|
|
|
|
|
|
|
} |
1486
|
|
|
|
|
|
|
elsif ($dtype eq 'number') { |
1487
|
|
|
|
|
|
|
return undef unless ( |
1488
|
0
|
0
|
|
|
|
0
|
looks_like_number( $query ) |
1489
|
|
|
|
|
|
|
); |
1490
|
0
|
|
|
|
|
0
|
$mode = 'exact'; |
1491
|
|
|
|
|
|
|
} |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
# Special-case: pre-validate enums (Github Issue #56) |
1494
|
0
|
|
|
|
|
0
|
my $enumVh = $cnf->{enum_value_hash}; |
1495
|
0
|
0
|
|
|
|
0
|
if ($enumVh) { |
1496
|
0
|
0
|
|
|
|
0
|
return undef unless ($enumVh->{$query}); |
1497
|
0
|
|
|
|
|
0
|
$mode = 'exact'; |
1498
|
|
|
|
|
|
|
} |
1499
|
|
|
|
|
|
|
|
1500
|
|
|
|
|
|
|
# New for GitHub Issue #97 |
1501
|
0
|
|
|
|
|
0
|
my $strf = $cnf->{search_operator_strf}; |
1502
|
0
|
0
|
|
0
|
|
0
|
my $s = $strf ? sub { sprintf($strf,shift) } : sub { shift }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
# 'text' is the only type which can do a LIKE (i.e. sub-string) |
1505
|
0
|
0
|
|
|
|
0
|
return $mode eq 'like' |
1506
|
|
|
|
|
|
|
? $self->_op_fuse($dbicname => { $s->('like') => join('%','',$query,'') }) |
1507
|
|
|
|
|
|
|
: $self->_op_fuse($dbicname => { $s->('=') => $query }); |
1508
|
|
|
|
|
|
|
} |
1509
|
|
|
|
|
|
|
|
1510
|
|
|
|
|
|
|
|
1511
|
|
|
|
|
|
|
|
1512
|
|
|
|
|
|
|
our ($needs_having,$dbf_active_conditions); |
1513
|
|
|
|
|
|
|
|
1514
|
|
|
|
|
|
|
# Applies multifilter search to ResultSet: |
1515
|
|
|
|
|
|
|
sub chain_Rs_req_multifilter { |
1516
|
8
|
|
|
8
|
0
|
21
|
my $self = shift; |
1517
|
8
|
|
33
|
|
|
42
|
my $Rs = shift || $self->_ResultSet; |
1518
|
8
|
|
33
|
|
|
38
|
my $params = shift || $self->c->req->params; |
1519
|
|
|
|
|
|
|
|
1520
|
8
|
|
|
|
|
43
|
my $multifilter = $self->param_decodeIf($params->{multifilter},[]); |
1521
|
8
|
|
|
|
|
38
|
my $multifilter_frozen = $self->param_decodeIf($params->{multifilter_frozen},[]); |
1522
|
8
|
|
|
|
|
34
|
@$multifilter = (@$multifilter_frozen,@$multifilter); |
1523
|
|
|
|
|
|
|
|
1524
|
8
|
50
|
|
|
|
47
|
return $Rs unless (scalar @$multifilter > 0); |
1525
|
|
|
|
|
|
|
|
1526
|
|
|
|
|
|
|
# Localize HAVING tracking global variables. These will be set within the call chain |
1527
|
|
|
|
|
|
|
# from 'multifilter_to_dbf' called next; |
1528
|
0
|
|
|
|
|
0
|
local $needs_having = 0; |
1529
|
0
|
|
|
|
|
0
|
local $dbf_active_conditions = []; |
1530
|
|
|
|
|
|
|
|
1531
|
0
|
|
|
|
|
0
|
my $attr = { join => {} }; |
1532
|
0
|
|
0
|
|
|
0
|
my $cond = $self->multifilter_to_dbf($multifilter,$attr->{join}) || {}; |
1533
|
|
|
|
|
|
|
|
1534
|
0
|
0
|
|
|
|
0
|
return $self->_chain_search_rs($Rs,$cond,$attr) unless ($needs_having); |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
# If we're here, '$needs_having' was set to true and we need to convert the |
1537
|
|
|
|
|
|
|
# *entire* query to use HAVING instead of WHERE to be sure we correctly handle |
1538
|
|
|
|
|
|
|
# any possible interdependent hierachy of '-and'/'-or' conditions. This means that |
1539
|
|
|
|
|
|
|
# one or more of our columns are virtual, and one or more of them are contained |
1540
|
|
|
|
|
|
|
# within the multifilter search, which effects the entire multifilter query. |
1541
|
|
|
|
|
|
|
# |
1542
|
|
|
|
|
|
|
# To convert from WHERE to HAVING we need to convert ALL columns to act like |
1543
|
|
|
|
|
|
|
# virtual columns with '-as' and then transform the conditions to reference those |
1544
|
|
|
|
|
|
|
# -as/alias names. Also, we need to make sure that each condition exists in the |
1545
|
|
|
|
|
|
|
# SELECT clause of the query for it to be able to work as a HAVING condition, |
1546
|
|
|
|
|
|
|
# because HAVING references the declared AS name from the SELECT, while WHERE is |
1547
|
|
|
|
|
|
|
# based on real/existing column names of the schema. Note that we're doing this |
1548
|
|
|
|
|
|
|
# because we have to; when there are no virtual columns in the condition we do |
1549
|
|
|
|
|
|
|
# a nomal WHERE which provides better performance. |
1550
|
|
|
|
|
|
|
# |
1551
|
|
|
|
|
|
|
# TODO: investigate teasing out exactly which conditions really have to use HAVING |
1552
|
|
|
|
|
|
|
# and which others could continue to use WHERE without harming the overall effective |
1553
|
|
|
|
|
|
|
# result set. This could get very complicated because the condition data structure |
1554
|
|
|
|
|
|
|
# supports an arbitrary structure. It is doable, but it depends on the real-world |
1555
|
|
|
|
|
|
|
# performance differences to determine how important that extra layer of logic would |
1556
|
|
|
|
|
|
|
# really be. |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
# |
1559
|
|
|
|
|
|
|
# Step 1/3 - add missing selects |
1560
|
|
|
|
|
|
|
# |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
# sort virtual selects to the end for priority in name collisions |
1563
|
|
|
|
|
|
|
# (can happen with multi-rels with the same name as a column): |
1564
|
0
|
|
|
|
|
0
|
@$dbf_active_conditions = sort { !(ref $b->{select}) cmp (ref $a->{select}) } @$dbf_active_conditions; |
|
0
|
|
|
|
|
0
|
|
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
# Collapse uniq needed col/cond names into a Hash: |
1568
|
0
|
|
|
|
|
0
|
my %needed_selects = map { $_->{name} => $_ } @$dbf_active_conditions; |
|
0
|
|
|
|
|
0
|
|
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
# ---- Hack/fix for searching non-active virtual columns: |
1571
|
|
|
|
|
|
|
# the problem with this $dbf_active_conditions global/local design is that |
1572
|
|
|
|
|
|
|
# it will not contain *virtual* columns that are not being selected in |
1573
|
|
|
|
|
|
|
# active columns. This breaks virtual columns from being able to be filtered |
1574
|
|
|
|
|
|
|
# while not active. To solve this we just need to manually resolve the column |
1575
|
|
|
|
|
|
|
# into its proper dbic column select name: |
1576
|
0
|
|
|
|
|
0
|
for my $fname (keys %needed_selects) { |
1577
|
0
|
|
|
|
|
0
|
my $hash = $needed_selects{$fname}; |
1578
|
0
|
|
|
|
|
0
|
$hash->{select} = $self->resolve_dbic_colname($hash->{field},{}); |
1579
|
|
|
|
|
|
|
} |
1580
|
|
|
|
|
|
|
# ---- |
1581
|
|
|
|
|
|
|
|
1582
|
0
|
|
|
|
|
0
|
my $cur_select = $Rs->{attrs}->{select}; |
1583
|
0
|
|
|
|
|
0
|
my $cur_as = $Rs->{attrs}->{as}; |
1584
|
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
# prune out all columns that are already being selected: |
1586
|
|
|
|
|
|
|
exists $needed_selects{$_} and delete $needed_selects{$_} |
1587
|
0
|
0
|
0
|
0
|
|
0
|
for (map { try{$_->{-as}} || $_ } @$cur_select); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1588
|
|
|
|
|
|
|
|
1589
|
|
|
|
|
|
|
# Add all leftover needed selects. These are column/cond/select names that are being |
1590
|
|
|
|
|
|
|
# used in one or more conditions but are not already being selected. Unlike WHERE, all |
1591
|
|
|
|
|
|
|
# HAVING conditions must be contained in the SELECT clause: |
1592
|
|
|
|
|
|
|
push(@$cur_select,$needed_selects{$_}->{select}) |
1593
|
0
|
|
0
|
|
|
0
|
and push(@$cur_as,$needed_selects{$_}->{field}) for (keys %needed_selects); |
1594
|
|
|
|
|
|
|
|
1595
|
|
|
|
|
|
|
# |
1596
|
|
|
|
|
|
|
# Step 2/3 - transform selects: |
1597
|
|
|
|
|
|
|
# |
1598
|
|
|
|
|
|
|
|
1599
|
0
|
|
|
|
|
0
|
my %virtuals = (); #<-- new for Github Issue #51 |
1600
|
0
|
|
|
|
|
0
|
my %map = (); |
1601
|
0
|
|
|
|
|
0
|
my ($select,$as) = ([],[]); |
1602
|
0
|
|
|
|
|
0
|
for my $i (0..$#$cur_select) { |
1603
|
0
|
0
|
|
|
|
0
|
delete $needed_selects{$cur_select->[$i]} if (exists $needed_selects{$cur_select->[$i]}); |
1604
|
0
|
|
|
|
|
0
|
push @$as, $cur_as->[$i]; |
1605
|
0
|
0
|
|
|
|
0
|
if (ref $cur_select->[$i]) { |
1606
|
|
|
|
|
|
|
# Already a virtual column, no changes: |
1607
|
0
|
|
|
|
|
0
|
push @$select, $cur_select->[$i]; |
1608
|
|
|
|
|
|
|
# new for Github Issue #51: |
1609
|
0
|
|
|
|
|
0
|
$virtuals{$cur_as->[$i]} = $self->_extract_virtual_subselect_ref($cur_select->[$i]); |
1610
|
0
|
|
|
|
|
0
|
next; |
1611
|
|
|
|
|
|
|
} |
1612
|
|
|
|
|
|
|
|
1613
|
0
|
|
|
|
|
0
|
push @$select, { '' => $cur_select->[$i], '-as' => $cur_as->[$i] }; |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
# Track the mapping so we can walk/replace the cond in the next step: |
1616
|
0
|
|
|
|
|
0
|
$map{$cur_select->[$i]} = $cur_as->[$i]; |
1617
|
|
|
|
|
|
|
} |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
# |
1620
|
|
|
|
|
|
|
# Step 3/3 - transform conditions: |
1621
|
|
|
|
|
|
|
# |
1622
|
|
|
|
|
|
|
|
1623
|
|
|
|
|
|
|
# Deep remap all condition values from WHERE type to HAVING (AS) type: |
1624
|
|
|
|
|
|
|
my ($having) = dmap { ref $_ eq 'HASH' ? |
1625
|
|
|
|
|
|
|
# wtf? dmap doesn't act on keys, so we have to do it ourselves. |
1626
|
|
|
|
|
|
|
# We only care about keys, anyway |
1627
|
0
|
0
|
0
|
0
|
|
0
|
{ map { defined $_ && exists $map{$_} ? $map{$_} : $_ } %$_ } : |
|
0
|
0
|
|
|
|
0
|
|
1628
|
|
|
|
|
|
|
$_ |
1629
|
0
|
|
|
|
|
0
|
} $cond; |
1630
|
|
|
|
|
|
|
|
1631
|
|
|
|
|
|
|
# --- |
1632
|
|
|
|
|
|
|
# Temporary implementation for Github Issue #51 |
1633
|
|
|
|
|
|
|
# Here we are doing yet another transformation step, which is to duplicate the full sub-select |
1634
|
|
|
|
|
|
|
# for our virtual columns within the condition. This was needed for PostgreSQL support which |
1635
|
|
|
|
|
|
|
# was discussed at length within the comments of Github Issue #51. Since we're doing it this |
1636
|
|
|
|
|
|
|
# way now, we can use a normal WHERE clause instead of a HAVING clause. I'm still not certain |
1637
|
|
|
|
|
|
|
# this represents the final implementation, and there are lots of entanglements and potential |
1638
|
|
|
|
|
|
|
# points-of-failure (which are not yet under test coverage) so for now this is being done using |
1639
|
|
|
|
|
|
|
# the least code changes possible. If this is finalized, a refactor pass will remove a *lot* of |
1640
|
|
|
|
|
|
|
# code and machinery that serves no purpose if we are not transforming into a HAVING at all... |
1641
|
|
|
|
|
|
|
# |
1642
|
0
|
|
|
|
|
0
|
my $virtual_where = 1; #<-- set to 0 to revert to HAVING codepath |
1643
|
0
|
0
|
|
|
|
0
|
if ($virtual_where) { |
1644
|
0
|
|
|
|
|
0
|
$cond = $self->_recurse_transform_condition(clone($cond),\%virtuals); |
1645
|
0
|
|
|
|
|
0
|
return $self->_chain_search_rs($Rs,{},{ %$attr, |
1646
|
|
|
|
|
|
|
where => $cond, |
1647
|
|
|
|
|
|
|
select => $select, |
1648
|
|
|
|
|
|
|
as => $as |
1649
|
|
|
|
|
|
|
}); |
1650
|
|
|
|
|
|
|
} |
1651
|
|
|
|
|
|
|
else { |
1652
|
|
|
|
|
|
|
# This is the old code which uses HAVING via alias identifiers. This is being left in for |
1653
|
|
|
|
|
|
|
# now as an active code block (rather than removed/commented out) to make it easier to |
1654
|
|
|
|
|
|
|
# come back to later. We may want to still do this for RDBMS'es which support this (at |
1655
|
|
|
|
|
|
|
# least MySQL and SQLite do, and at least PostgreSQL does not). But, the question will be |
1656
|
|
|
|
|
|
|
# to ask if there is even a performance advantage of doing this, and if so, when, how, etc |
1657
|
|
|
|
|
|
|
return $self->_chain_search_rs($Rs,{},{ %$attr, |
1658
|
0
|
|
|
|
|
0
|
group_by => [ map { 'me.' . $_ } @{$self->primary_columns} ], #<-- safe group_by |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1659
|
|
|
|
|
|
|
having => $having, |
1660
|
|
|
|
|
|
|
select => $select, |
1661
|
|
|
|
|
|
|
as => $as |
1662
|
|
|
|
|
|
|
}); |
1663
|
|
|
|
|
|
|
} |
1664
|
|
|
|
|
|
|
# --- |
1665
|
|
|
|
|
|
|
} |
1666
|
|
|
|
|
|
|
|
1667
|
|
|
|
|
|
|
|
1668
|
|
|
|
|
|
|
# This machinery was added for Github Issue #51 (see earlier comments) |
1669
|
|
|
|
|
|
|
sub _extract_virtual_subselect_ref { |
1670
|
0
|
|
|
0
|
|
0
|
my ($self, $el) = @_; |
1671
|
0
|
0
|
|
|
|
0
|
my $val = $el->{''} or die "Expected empty-string hashkey"; |
1672
|
|
|
|
|
|
|
# We're handling just 2 cases which know about in advance, virtual columns |
1673
|
|
|
|
|
|
|
# and multi-relationship columns: |
1674
|
0
|
0
|
|
|
|
0
|
$val = ref($val) eq 'ARRAY' ? $val->[0] : $val; |
1675
|
0
|
0
|
|
|
|
0
|
return ref $val ? $val : \$val; |
1676
|
|
|
|
|
|
|
} |
1677
|
|
|
|
|
|
|
|
1678
|
0
|
|
|
0
|
0
|
0
|
sub sql_maker { (shift)->ResultSource->schema->storage->sql_maker } |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
sub _recurse_transform_condition { |
1681
|
0
|
|
|
0
|
|
0
|
my ($self, $val, $remap) = @_; |
1682
|
|
|
|
|
|
|
|
1683
|
0
|
0
|
0
|
|
|
0
|
return $val unless ($val && ref $val); |
1684
|
|
|
|
|
|
|
|
1685
|
0
|
0
|
|
|
|
0
|
if(ref($val) eq 'ARRAY') { |
|
|
0
|
|
|
|
|
|
1686
|
|
|
|
|
|
|
@$val = map { |
1687
|
0
|
|
|
|
|
0
|
$self->_recurse_transform_condition($_,$remap) |
|
0
|
|
|
|
|
0
|
|
1688
|
|
|
|
|
|
|
} @$val; |
1689
|
|
|
|
|
|
|
} |
1690
|
|
|
|
|
|
|
elsif(ref($val) eq 'HASH') { |
1691
|
0
|
0
|
|
|
|
0
|
if(scalar(keys %$val) == 1) { |
1692
|
0
|
|
|
|
|
0
|
my ($k,$v) = (%$val); |
1693
|
|
|
|
|
|
|
# This is the location where we are actually |
1694
|
|
|
|
|
|
|
# changing something in the structure: |
1695
|
|
|
|
|
|
|
return &_binary_op_fuser( |
1696
|
|
|
|
|
|
|
$self->sql_maker, |
1697
|
|
|
|
|
|
|
$remap->{$k}, |
1698
|
|
|
|
|
|
|
$self->_recurse_transform_condition($v,$remap) |
1699
|
0
|
0
|
|
|
|
0
|
) if($remap->{$k}); |
1700
|
|
|
|
|
|
|
} |
1701
|
|
|
|
|
|
|
|
1702
|
|
|
|
|
|
|
%$val = map { |
1703
|
0
|
|
|
|
|
0
|
$_ => $self->_recurse_transform_condition($val->{$_},$remap) |
|
0
|
|
|
|
|
0
|
|
1704
|
|
|
|
|
|
|
} (keys %$val); |
1705
|
|
|
|
|
|
|
} |
1706
|
|
|
|
|
|
|
|
1707
|
0
|
|
|
|
|
0
|
return $val; |
1708
|
|
|
|
|
|
|
} |
1709
|
|
|
|
|
|
|
|
1710
|
|
|
|
|
|
|
# -- Function (and disclaimer) provided by ribasushi for Github Issue #51 -- |
1711
|
|
|
|
|
|
|
############################################################### |
1712
|
|
|
|
|
|
|
# DO NOT COPY THIS UNDER ANY CIRCUMSTANCES |
1713
|
|
|
|
|
|
|
# THIS IS A TEMPORARY HACK AND WILL BE BROKEN BY THE MAINTAINERS |
1714
|
|
|
|
|
|
|
# POSSIBLY BEFORE THE END OF THIS YEAR |
1715
|
|
|
|
|
|
|
############################################################### |
1716
|
|
|
|
|
|
|
sub _binary_op_fuser { |
1717
|
10
|
|
|
10
|
|
120032
|
my ($sm, $l, $r) = @_; |
1718
|
|
|
|
|
|
|
|
1719
|
10
|
|
|
|
|
38
|
my ($lsql, @lbind) = $sm->_recurse_where($l); |
1720
|
|
|
|
|
|
|
|
1721
|
10
|
|
|
|
|
506
|
local $sm->{_nested_func_lhs} = {}; |
1722
|
10
|
|
|
|
|
37
|
my ($rsql, @rbind) = $sm->_recurse_where({ "\0" => $r }); |
1723
|
|
|
|
|
|
|
|
1724
|
10
|
|
|
|
|
3263
|
my ($ql, $qr) = $sm->_quote_chars; |
1725
|
10
|
|
|
|
|
187
|
$rsql =~ s/ (\Q$ql\E)? \0 (\Q$qr\E)? //gx; |
1726
|
|
|
|
|
|
|
|
1727
|
10
|
|
|
|
|
44
|
$rsql =~ s/ \A \s* \( (.+?) \) \s* \z /$1/sx; |
1728
|
|
|
|
|
|
|
|
1729
|
|
|
|
|
|
|
return \[ |
1730
|
10
|
|
|
|
|
86
|
"$lsql $rsql", |
1731
|
|
|
|
|
|
|
@lbind, |
1732
|
|
|
|
|
|
|
@rbind |
1733
|
|
|
|
|
|
|
]; |
1734
|
|
|
|
|
|
|
} |
1735
|
|
|
|
|
|
|
############################################################### |
1736
|
|
|
|
|
|
|
# DO NOT COPY THIS UNDER ANY CIRCUMSTANCES |
1737
|
|
|
|
|
|
|
# THIS IS A TEMPORARY HACK AND WILL BE BROKEN BY THE MAINTAINERS |
1738
|
|
|
|
|
|
|
# POSSIBLY BEFORE THE END OF THIS YEAR |
1739
|
|
|
|
|
|
|
############################################################### |
1740
|
|
|
|
|
|
|
# -- |
1741
|
|
|
|
|
|
|
|
1742
|
|
|
|
|
|
|
|
1743
|
|
|
|
|
|
|
# Common proxy for calls to $Rs->search_rs(...) |
1744
|
|
|
|
|
|
|
sub _chain_search_rs { |
1745
|
27
|
|
|
27
|
|
75
|
my ($self, $Rs, $cond, $attr) = @_; |
1746
|
|
|
|
|
|
|
|
1747
|
|
|
|
|
|
|
# -- |
1748
|
|
|
|
|
|
|
# Convert {} joins to undef - this prevents ResultSet unititialized warnings when: |
1749
|
|
|
|
|
|
|
# join => { rel1 => { rel2 => {} } } |
1750
|
|
|
|
|
|
|
# becomes: |
1751
|
|
|
|
|
|
|
# join => { rel1 => { rel2 => undef } } |
1752
|
|
|
|
|
|
|
# (See DBIx::Class::ResultSet::_calculate_score() and related code) |
1753
|
|
|
|
|
|
|
$attr = { |
1754
|
|
|
|
|
|
|
%$attr, |
1755
|
|
|
|
|
|
|
join => $self->_recurse_clean_empty_hashrefs($attr->{join}) |
1756
|
27
|
100
|
|
|
|
147
|
} if ($attr->{join}); |
1757
|
|
|
|
|
|
|
# -- |
1758
|
|
|
|
|
|
|
|
1759
|
27
|
|
|
|
|
124
|
$Rs->search_rs($cond,$attr) |
1760
|
|
|
|
|
|
|
} |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
sub _recurse_clean_empty_hashrefs { |
1763
|
8
|
|
|
8
|
|
28
|
my ($self, $val) = @_; |
1764
|
|
|
|
|
|
|
|
1765
|
8
|
50
|
33
|
|
|
63
|
if($val && ref($val) eq 'HASH') { |
1766
|
|
|
|
|
|
|
return (scalar keys(%$val) > 0) |
1767
|
8
|
50
|
|
|
|
67
|
? { map { $_ => $self->_recurse_clean_empty_hashrefs($val->{$_}) } keys(%$val) } |
|
0
|
|
|
|
|
0
|
|
1768
|
|
|
|
|
|
|
: undef |
1769
|
|
|
|
|
|
|
} |
1770
|
|
|
|
|
|
|
else { |
1771
|
0
|
|
|
|
|
0
|
return $val |
1772
|
|
|
|
|
|
|
} |
1773
|
|
|
|
|
|
|
} |
1774
|
|
|
|
|
|
|
|
1775
|
|
|
|
|
|
|
|
1776
|
|
|
|
|
|
|
sub multifilter_to_dbf { |
1777
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1778
|
0
|
|
|
|
|
0
|
my $multi = clone(shift); |
1779
|
0
|
|
0
|
|
|
0
|
my $join = shift || {}; |
1780
|
|
|
|
|
|
|
|
1781
|
0
|
0
|
|
|
|
0
|
return $self->multifilter_to_dbf({ '-and' => $multi },$join) if (ref($multi) eq 'ARRAY'); |
1782
|
|
|
|
|
|
|
|
1783
|
0
|
0
|
0
|
|
|
0
|
die RED.BOLD."Invalid multifilter:\n" . Dumper($multi).CLEAR unless ( |
1784
|
|
|
|
|
|
|
ref($multi) eq 'HASH' and |
1785
|
|
|
|
|
|
|
keys %$multi == 1 |
1786
|
|
|
|
|
|
|
); |
1787
|
|
|
|
|
|
|
|
1788
|
0
|
|
|
|
|
0
|
my ($f,$cond) = (%$multi); |
1789
|
0
|
0
|
0
|
|
|
0
|
if($f eq '-and' or $f eq '-or') { |
1790
|
0
|
0
|
|
|
|
0
|
die "-and/-or must reference an ARRAY/LIST" unless (ref($cond) eq 'ARRAY'); |
1791
|
0
|
|
|
|
|
0
|
my @list = map { $self->multifilter_to_dbf($_,$join) } @$cond; |
|
0
|
|
|
|
|
0
|
|
1792
|
0
|
|
|
|
|
0
|
return { $f => \@list }; |
1793
|
|
|
|
|
|
|
} |
1794
|
|
|
|
|
|
|
|
1795
|
|
|
|
|
|
|
# -- relationship column: |
1796
|
|
|
|
|
|
|
my $is_cond = ( |
1797
|
|
|
|
|
|
|
ref($cond) eq 'HASH' and |
1798
|
|
|
|
|
|
|
exists $cond->{is} |
1799
|
0
|
0
|
0
|
|
|
0
|
) ? 1 : 0; |
1800
|
|
|
|
|
|
|
|
1801
|
0
|
|
0
|
|
|
0
|
my $column = $self->get_column($f) || {}; |
1802
|
0
|
|
0
|
|
|
0
|
$f = $column->{query_search_use_column} || $f; |
1803
|
0
|
0
|
0
|
|
|
0
|
$f = $column->{query_id_use_column} || $f if ($is_cond); |
1804
|
|
|
|
|
|
|
# -- |
1805
|
|
|
|
|
|
|
|
1806
|
0
|
0
|
|
|
|
0
|
my $dbfName = $self->resolve_dbic_colname($f,$join) |
1807
|
|
|
|
|
|
|
or die "Client supplied Unknown multifilter-field '$f' in Ext Query!"; |
1808
|
|
|
|
|
|
|
|
1809
|
|
|
|
|
|
|
# Set the localized '$needs_having' flag to tell our caller to convert |
1810
|
|
|
|
|
|
|
# from WHERE to HAVING if this condition is based on a virtual column: |
1811
|
|
|
|
|
|
|
$needs_having = 1 if( |
1812
|
|
|
|
|
|
|
ref $dbfName eq 'HASH' and |
1813
|
|
|
|
|
|
|
exists $dbfName->{-as} and |
1814
|
0
|
0
|
0
|
|
|
0
|
exists $dbfName->{''} |
|
|
|
0
|
|
|
|
|
1815
|
|
|
|
|
|
|
); |
1816
|
|
|
|
|
|
|
|
1817
|
0
|
|
|
|
|
0
|
return $self->multifilter_translate_cond($cond,$dbfName,$f); |
1818
|
|
|
|
|
|
|
} |
1819
|
|
|
|
|
|
|
|
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
|
1822
|
|
|
|
|
|
|
my %mf_op_alias = ( |
1823
|
|
|
|
|
|
|
'is' => '=', |
1824
|
|
|
|
|
|
|
'equal to' => '=', |
1825
|
|
|
|
|
|
|
'is equal to' => '=', |
1826
|
|
|
|
|
|
|
'exactly' => '=', |
1827
|
|
|
|
|
|
|
'before' => '<', |
1828
|
|
|
|
|
|
|
'less than' => '<', |
1829
|
|
|
|
|
|
|
'greater than' => '>', |
1830
|
|
|
|
|
|
|
'after' => '>', |
1831
|
|
|
|
|
|
|
'not equal to' => '!=', |
1832
|
|
|
|
|
|
|
'is not equal to' => '!=', |
1833
|
|
|
|
|
|
|
"doesn't contain" => 'not_contain', |
1834
|
|
|
|
|
|
|
'starts with' => 'starts_with', |
1835
|
|
|
|
|
|
|
'ends with' => 'ends_with', |
1836
|
|
|
|
|
|
|
"doesn't start with" => 'not_starts_with', |
1837
|
|
|
|
|
|
|
"doesn't end with" => 'not_ends_with', |
1838
|
|
|
|
|
|
|
'ends with' => 'ends_with', |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
'is null' => 'is_null', |
1841
|
|
|
|
|
|
|
'is empty' => 'is_empty', |
1842
|
|
|
|
|
|
|
'is not null' => 'not_null', |
1843
|
|
|
|
|
|
|
'is not empty' => 'not_empty', |
1844
|
|
|
|
|
|
|
'is null or empty' => 'null_or_empty', |
1845
|
|
|
|
|
|
|
'is not null or empty' => 'not_null_or_empty', |
1846
|
|
|
|
|
|
|
|
1847
|
|
|
|
|
|
|
'null/empty status' => 'null_empty_status' |
1848
|
|
|
|
|
|
|
); |
1849
|
|
|
|
|
|
|
# This will deep recurse if there there a circular refs in %mf_op_alias above |
1850
|
|
|
|
|
|
|
sub _mf_resolve_op { |
1851
|
0
|
|
|
0
|
|
0
|
my ($self, $op) = @_; |
1852
|
0
|
0
|
|
|
|
0
|
$mf_op_alias{$op} ? $self->_mf_resolve_op($mf_op_alias{$op}) : $op; |
1853
|
|
|
|
|
|
|
} |
1854
|
|
|
|
|
|
|
|
1855
|
|
|
|
|
|
|
sub _mf_get_cond { |
1856
|
0
|
|
|
0
|
|
0
|
my ($self,$select,$op,$val,$strf) = @_; |
1857
|
|
|
|
|
|
|
|
1858
|
0
|
|
|
|
|
0
|
$op = $self->_mf_resolve_op($op); |
1859
|
|
|
|
|
|
|
|
1860
|
|
|
|
|
|
|
# New for GitHub Issue #97 |
1861
|
0
|
0
|
|
0
|
|
0
|
my $s = $strf ? sub { sprintf($strf,shift) } : sub { shift }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1862
|
|
|
|
|
|
|
|
1863
|
0
|
|
|
|
|
0
|
my $cond; |
1864
|
|
|
|
|
|
|
|
1865
|
0
|
0
|
|
|
|
0
|
if($op eq 'contains') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1866
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $s->('like') => join('','%',$val,'%') }); |
1867
|
|
|
|
|
|
|
} |
1868
|
|
|
|
|
|
|
elsif($op eq 'starts_with') { |
1869
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $s->('like') => join('',$val,'%') }); |
1870
|
|
|
|
|
|
|
} |
1871
|
|
|
|
|
|
|
elsif($op eq 'ends_with') { |
1872
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $s->('like') => join('','%',$val) }); |
1873
|
|
|
|
|
|
|
} |
1874
|
|
|
|
|
|
|
elsif($op eq 'not_contain') { |
1875
|
0
|
|
|
|
|
0
|
$cond = { -or => [ # NOT LIKE -OR- NULL |
1876
|
|
|
|
|
|
|
$self->_op_fuse($select => { $s->('not like') => join('','%',$val,'%') }), |
1877
|
|
|
|
|
|
|
# Note: we do not pass the operator for undef through the strf because it |
1878
|
|
|
|
|
|
|
# is treated special by SQLA - becomes "IS NULL" etc... (#97) |
1879
|
|
|
|
|
|
|
$self->_op_fuse($select => { '=' => undef }), |
1880
|
|
|
|
|
|
|
]}; |
1881
|
|
|
|
|
|
|
} |
1882
|
|
|
|
|
|
|
elsif($op eq 'not_starts_with') { |
1883
|
0
|
|
|
|
|
0
|
$cond = { -or => [ # NOT LIKE -OR- NULL |
1884
|
|
|
|
|
|
|
$self->_op_fuse($select => { $s->('not like') => join('',$val,'%') }), |
1885
|
|
|
|
|
|
|
$self->_op_fuse($select => { '=' => undef }), |
1886
|
|
|
|
|
|
|
]}; |
1887
|
|
|
|
|
|
|
} |
1888
|
|
|
|
|
|
|
elsif($op eq 'not_ends_with') { |
1889
|
0
|
|
|
|
|
0
|
$cond = { -or => [ # NOT LIKE -OR- NULL |
1890
|
|
|
|
|
|
|
$self->_op_fuse($select => { $s->('not like') => join('','%',$val) }), |
1891
|
|
|
|
|
|
|
$self->_op_fuse($select => { '=' => undef }), |
1892
|
|
|
|
|
|
|
]}; |
1893
|
|
|
|
|
|
|
} |
1894
|
|
|
|
|
|
|
elsif($op eq 'is_null') { |
1895
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { '=' => undef }); |
1896
|
|
|
|
|
|
|
} |
1897
|
|
|
|
|
|
|
elsif($op eq 'is_empty') { |
1898
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $s->('=') => '' }); |
1899
|
|
|
|
|
|
|
} |
1900
|
|
|
|
|
|
|
elsif($op eq 'not_null') { |
1901
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { '!=' => undef }); |
1902
|
|
|
|
|
|
|
} |
1903
|
|
|
|
|
|
|
elsif($op eq 'not_empty') { |
1904
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $s->('!=') => '' }); |
1905
|
|
|
|
|
|
|
} |
1906
|
|
|
|
|
|
|
elsif($op eq 'null_or_empty') { |
1907
|
0
|
|
|
|
|
0
|
$cond = { -or => [ |
1908
|
|
|
|
|
|
|
$self->_op_fuse($select => { '=' => undef }), |
1909
|
|
|
|
|
|
|
$self->_op_fuse($select => { $s->('=') => '' }) |
1910
|
|
|
|
|
|
|
]}; |
1911
|
|
|
|
|
|
|
} |
1912
|
|
|
|
|
|
|
elsif($op eq 'not_null_or_empty') { |
1913
|
0
|
|
|
|
|
0
|
$cond = { -and => [ |
1914
|
|
|
|
|
|
|
$self->_op_fuse($select => { '!=' => undef }), |
1915
|
|
|
|
|
|
|
$self->_op_fuse($select => { $s->('!=') => '' }) |
1916
|
|
|
|
|
|
|
]}; |
1917
|
|
|
|
|
|
|
} |
1918
|
|
|
|
|
|
|
elsif($op eq 'null_empty_status') { |
1919
|
|
|
|
|
|
|
# Re-call with with the val as the op: |
1920
|
0
|
|
|
|
|
0
|
$cond = $self->_mf_get_cond($select, $val); |
1921
|
|
|
|
|
|
|
} |
1922
|
|
|
|
|
|
|
else { |
1923
|
0
|
|
|
|
|
0
|
$cond = $self->_op_fuse($select => { $op => $val }); |
1924
|
|
|
|
|
|
|
} |
1925
|
|
|
|
|
|
|
|
1926
|
0
|
|
|
|
|
0
|
$cond |
1927
|
|
|
|
|
|
|
} |
1928
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
sub _op_fuse { |
1930
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1931
|
0
|
|
|
|
|
0
|
&_binary_op_fuser($self->sql_maker, @_) |
1932
|
|
|
|
|
|
|
} |
1933
|
|
|
|
|
|
|
|
1934
|
|
|
|
|
|
|
|
1935
|
|
|
|
|
|
|
# -- multifilter_translate_cond() |
1936
|
|
|
|
|
|
|
# - refactored for #88 and #89 |
1937
|
|
|
|
|
|
|
# - previously modified for #69 and #51 |
1938
|
|
|
|
|
|
|
sub multifilter_translate_cond { |
1939
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1940
|
0
|
|
|
|
|
0
|
my $cond = shift; |
1941
|
0
|
|
|
|
|
0
|
my $dbfName = shift; |
1942
|
0
|
|
|
|
|
0
|
my $field = shift; |
1943
|
0
|
|
0
|
0
|
|
0
|
my $column = try{$self->get_column($field)} || {}; |
|
0
|
|
|
|
|
0
|
|
1944
|
|
|
|
|
|
|
|
1945
|
|
|
|
|
|
|
# If we're a virtual column: |
1946
|
|
|
|
|
|
|
my ($select,$as) = ((ref $dbfName||'') eq 'HASH' && $dbfName->{-as} && $dbfName->{''}) |
1947
|
|
|
|
|
|
|
? ($dbfName->{''} => $dbfName->{-as} ) |
1948
|
0
|
0
|
0
|
|
|
0
|
: ($dbfName => $dbfName ); |
1949
|
|
|
|
|
|
|
|
1950
|
|
|
|
|
|
|
# -- TODO - this is legacy and needs to be investigated and removed |
1951
|
|
|
|
|
|
|
# Track in localized global: |
1952
|
0
|
|
|
|
|
0
|
push @$dbf_active_conditions, { |
1953
|
|
|
|
|
|
|
name => $as, |
1954
|
|
|
|
|
|
|
field => $field, |
1955
|
|
|
|
|
|
|
select => clone($dbfName) |
1956
|
|
|
|
|
|
|
}; |
1957
|
|
|
|
|
|
|
# -- |
1958
|
|
|
|
|
|
|
|
1959
|
|
|
|
|
|
|
# There should be exactly 1 key/value: |
1960
|
0
|
0
|
|
|
|
0
|
die "invalid multifilter condition: must have exactly 1 key/value pair:\n" . Dumper($cond) |
1961
|
|
|
|
|
|
|
unless (keys %$cond == 1); |
1962
|
|
|
|
|
|
|
|
1963
|
0
|
|
|
|
|
0
|
my ($k,$v) = (%$cond); |
1964
|
|
|
|
|
|
|
|
1965
|
|
|
|
|
|
|
$v = $self->inflate_multifilter_date($v) if( |
1966
|
|
|
|
|
|
|
$column->{multifilter_type} && |
1967
|
0
|
0
|
0
|
|
|
0
|
$column->{multifilter_type} =~ /^date/ |
1968
|
|
|
|
|
|
|
); |
1969
|
|
|
|
|
|
|
|
1970
|
|
|
|
|
|
|
# New for GitHub #97 - pass in optional new search_operator_strf param |
1971
|
0
|
|
|
|
|
0
|
return $self->_mf_get_cond($select, $k, $v,$column->{search_operator_strf}); |
1972
|
|
|
|
|
|
|
} |
1973
|
|
|
|
|
|
|
|
1974
|
|
|
|
|
|
|
|
1975
|
|
|
|
|
|
|
|
1976
|
|
|
|
|
|
|
sub multifilter_date_getKeywordDt { |
1977
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
1978
|
0
|
|
|
|
|
0
|
my $keyword = shift; |
1979
|
|
|
|
|
|
|
|
1980
|
0
|
|
|
|
|
0
|
$keyword =~ s/\s*//g; #<-- stip whitespace from the keyword |
1981
|
0
|
|
|
|
|
0
|
$keyword = lc($keyword); #<-- lowercase it |
1982
|
|
|
|
|
|
|
|
1983
|
0
|
|
|
|
|
0
|
my $dt = DateTime->now( time_zone => 'local' ); |
1984
|
|
|
|
|
|
|
|
1985
|
0
|
|
|
|
|
0
|
my $kw = $keyword; |
1986
|
0
|
0
|
|
|
|
0
|
if($kw eq 'now') { return $dt } |
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1987
|
|
|
|
|
|
|
|
1988
|
0
|
|
|
|
|
0
|
elsif($kw eq 'thisminute') { return DateTime->new( |
1989
|
|
|
|
|
|
|
year => $dt->year, |
1990
|
|
|
|
|
|
|
month => $dt->month, |
1991
|
|
|
|
|
|
|
day => $dt->day, |
1992
|
|
|
|
|
|
|
hour => $dt->hour, |
1993
|
|
|
|
|
|
|
minute => $dt->minute, |
1994
|
|
|
|
|
|
|
second => 0, |
1995
|
|
|
|
|
|
|
time_zone => 'local' |
1996
|
|
|
|
|
|
|
)} |
1997
|
|
|
|
|
|
|
|
1998
|
0
|
|
|
|
|
0
|
elsif($kw eq 'thishour') { return DateTime->new( |
1999
|
|
|
|
|
|
|
year => $dt->year, |
2000
|
|
|
|
|
|
|
month => $dt->month, |
2001
|
|
|
|
|
|
|
day => $dt->day, |
2002
|
|
|
|
|
|
|
hour => $dt->hour, |
2003
|
|
|
|
|
|
|
minute => 0, |
2004
|
|
|
|
|
|
|
second => 0, |
2005
|
|
|
|
|
|
|
time_zone => 'local' |
2006
|
|
|
|
|
|
|
)} |
2007
|
|
|
|
|
|
|
|
2008
|
0
|
|
|
|
|
0
|
elsif($kw eq 'thisday') { return DateTime->new( |
2009
|
|
|
|
|
|
|
year => $dt->year, |
2010
|
|
|
|
|
|
|
month => $dt->month, |
2011
|
|
|
|
|
|
|
day => $dt->day, |
2012
|
|
|
|
|
|
|
hour => 0, |
2013
|
|
|
|
|
|
|
minute => 0, |
2014
|
|
|
|
|
|
|
second => 0, |
2015
|
|
|
|
|
|
|
time_zone => 'local' |
2016
|
|
|
|
|
|
|
)} |
2017
|
|
|
|
|
|
|
|
2018
|
|
|
|
|
|
|
# same as thisday: |
2019
|
0
|
|
|
|
|
0
|
elsif($kw eq 'today') { return DateTime->new( |
2020
|
|
|
|
|
|
|
year => $dt->year, |
2021
|
|
|
|
|
|
|
month => $dt->month, |
2022
|
|
|
|
|
|
|
day => $dt->day, |
2023
|
|
|
|
|
|
|
hour => 0, |
2024
|
|
|
|
|
|
|
minute => 0, |
2025
|
|
|
|
|
|
|
second => 0, |
2026
|
|
|
|
|
|
|
time_zone => 'local' |
2027
|
|
|
|
|
|
|
)} |
2028
|
|
|
|
|
|
|
|
2029
|
|
|
|
|
|
|
elsif($kw eq 'thisweek') { |
2030
|
0
|
|
|
|
|
0
|
my $day = $dt->day_of_week; |
2031
|
|
|
|
|
|
|
#$day++; $day = 1 if ($day > 7); #<-- shift day 1 from Monday to Sunday |
2032
|
0
|
|
|
|
|
0
|
$dt = $dt->subtract( days => ($day - 1) ); |
2033
|
0
|
|
|
|
|
0
|
return DateTime->new( |
2034
|
|
|
|
|
|
|
year => $dt->year, |
2035
|
|
|
|
|
|
|
month => $dt->month, |
2036
|
|
|
|
|
|
|
day => $dt->day, |
2037
|
|
|
|
|
|
|
hour => 0, |
2038
|
|
|
|
|
|
|
minute => 0, |
2039
|
|
|
|
|
|
|
second => 0, |
2040
|
|
|
|
|
|
|
time_zone => 'local' |
2041
|
|
|
|
|
|
|
); |
2042
|
|
|
|
|
|
|
} |
2043
|
|
|
|
|
|
|
|
2044
|
0
|
|
|
|
|
0
|
elsif($kw eq 'thismonth') { return DateTime->new( |
2045
|
|
|
|
|
|
|
year => $dt->year, |
2046
|
|
|
|
|
|
|
month => $dt->month, |
2047
|
|
|
|
|
|
|
day => 1, |
2048
|
|
|
|
|
|
|
hour => 0, |
2049
|
|
|
|
|
|
|
minute => 0, |
2050
|
|
|
|
|
|
|
second => 0, |
2051
|
|
|
|
|
|
|
time_zone => 'local' |
2052
|
|
|
|
|
|
|
)} |
2053
|
|
|
|
|
|
|
|
2054
|
|
|
|
|
|
|
elsif($kw eq 'thisquarter') { |
2055
|
0
|
|
|
|
|
0
|
my $month = $dt->month; |
2056
|
0
|
|
|
|
|
0
|
my $subtract = 0; |
2057
|
0
|
0
|
0
|
|
|
0
|
if($month > 0 && $month <= 3) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
2058
|
0
|
|
|
|
|
0
|
$subtract = $month - 1; |
2059
|
|
|
|
|
|
|
} |
2060
|
|
|
|
|
|
|
elsif($month > 3 && $month <= 6) { |
2061
|
0
|
|
|
|
|
0
|
$subtract = $month - 4; |
2062
|
|
|
|
|
|
|
} |
2063
|
|
|
|
|
|
|
elsif($month > 6 && $month <= 9) { |
2064
|
0
|
|
|
|
|
0
|
$subtract = $month - 7; |
2065
|
|
|
|
|
|
|
} |
2066
|
|
|
|
|
|
|
else { |
2067
|
0
|
|
|
|
|
0
|
$subtract = $month - 10; |
2068
|
|
|
|
|
|
|
} |
2069
|
|
|
|
|
|
|
|
2070
|
0
|
|
|
|
|
0
|
$dt = $dt->subtract( months => $subtract ); |
2071
|
0
|
|
|
|
|
0
|
return DateTime->new( |
2072
|
|
|
|
|
|
|
year => $dt->year, |
2073
|
|
|
|
|
|
|
month => $dt->month, |
2074
|
|
|
|
|
|
|
day => 1, |
2075
|
|
|
|
|
|
|
hour => 0, |
2076
|
|
|
|
|
|
|
minute => 0, |
2077
|
|
|
|
|
|
|
second => 0, |
2078
|
|
|
|
|
|
|
time_zone => 'local' |
2079
|
|
|
|
|
|
|
); |
2080
|
|
|
|
|
|
|
} |
2081
|
|
|
|
|
|
|
|
2082
|
0
|
|
|
|
|
0
|
elsif($kw eq 'thisyear') { return DateTime->new( |
2083
|
|
|
|
|
|
|
year => $dt->year, |
2084
|
|
|
|
|
|
|
month => 1, |
2085
|
|
|
|
|
|
|
day => 1, |
2086
|
|
|
|
|
|
|
hour => 0, |
2087
|
|
|
|
|
|
|
minute => 0, |
2088
|
|
|
|
|
|
|
second => 0, |
2089
|
|
|
|
|
|
|
time_zone => 'local' |
2090
|
|
|
|
|
|
|
)} |
2091
|
|
|
|
|
|
|
|
2092
|
0
|
|
|
|
|
0
|
return undef; |
2093
|
|
|
|
|
|
|
} |
2094
|
|
|
|
|
|
|
|
2095
|
|
|
|
|
|
|
# This is a clone of the JavaScript logic in the function parseRelativeDate() in the plugin |
2096
|
|
|
|
|
|
|
# class Ext.ux.RapidApp.Plugin.RelativeDateTime. While it is not ideal to have to reproduce |
2097
|
|
|
|
|
|
|
# this and have to maintain in both Perl and JavaScript simultaneously, this is the most |
2098
|
|
|
|
|
|
|
# straightforward way to achive the desired functionality. This is because these relative |
2099
|
|
|
|
|
|
|
# dates have to be inflated at query/request time, and MultiFilters wasn't designed with that |
2100
|
|
|
|
|
|
|
# in mind. To do this in the client side, multifilters would need significant modifications |
2101
|
|
|
|
|
|
|
# to get it to munge its filters on every request, which is was not designed to do. |
2102
|
|
|
|
|
|
|
sub inflate_multifilter_date { |
2103
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2104
|
0
|
|
|
|
|
0
|
my $v = shift; |
2105
|
|
|
|
|
|
|
|
2106
|
0
|
|
|
|
|
0
|
my $dt = $self->multifilter_date_getKeywordDt($v); |
2107
|
0
|
0
|
|
|
|
0
|
return $dt->ymd . ' ' . $dt->hms if ($dt); |
2108
|
|
|
|
|
|
|
|
2109
|
0
|
|
|
|
|
0
|
my $orig_v = $v; |
2110
|
|
|
|
|
|
|
|
2111
|
0
|
|
|
|
|
0
|
my @parts = split(/[\-\+]/,$v); |
2112
|
0
|
0
|
0
|
|
|
0
|
if(scalar @parts > 1 && length $parts[0] > 0) { |
2113
|
|
|
|
|
|
|
#If we are here then it means a custom start keyword was specified: |
2114
|
0
|
|
|
|
|
0
|
my $keyword = $parts[0]; |
2115
|
0
|
|
|
|
|
0
|
$v =~ s/^${keyword}//; #<-- strip out the keyword from the string value |
2116
|
0
|
|
|
|
|
0
|
$keyword =~ s/\s*//g; #<-- stip whitespace from the keyword |
2117
|
0
|
|
|
|
|
0
|
$keyword = lc($keyword); #<-- lowercase it |
2118
|
|
|
|
|
|
|
|
2119
|
0
|
|
|
|
|
0
|
$dt = $self->multifilter_date_getKeywordDt($keyword); |
2120
|
|
|
|
|
|
|
} |
2121
|
|
|
|
|
|
|
else { |
2122
|
0
|
|
|
|
|
0
|
$dt = $self->multifilter_date_getKeywordDt('now'); |
2123
|
|
|
|
|
|
|
} |
2124
|
|
|
|
|
|
|
|
2125
|
0
|
|
|
|
|
0
|
my $sign = substr($v,0,1); |
2126
|
0
|
0
|
0
|
|
|
0
|
return $orig_v unless ($dt && ($sign eq '-' || $sign eq '+')); |
|
|
|
0
|
|
|
|
|
2127
|
|
|
|
|
|
|
|
2128
|
0
|
|
|
|
|
0
|
my $str = substr($v,1); |
2129
|
|
|
|
|
|
|
|
2130
|
|
|
|
|
|
|
# Strip whitespace and commas: |
2131
|
0
|
|
|
|
|
0
|
$str =~ s/[\s\,]*//g; |
2132
|
|
|
|
|
|
|
|
2133
|
0
|
|
|
|
|
0
|
$str = lc($str); |
2134
|
|
|
|
|
|
|
|
2135
|
0
|
|
|
|
|
0
|
@parts = (); |
2136
|
0
|
|
|
|
|
0
|
while(length $str) { |
2137
|
0
|
|
|
|
|
0
|
my ($num,$unit); |
2138
|
0
|
|
|
|
|
0
|
my $match; |
2139
|
0
|
|
|
|
|
0
|
($match) = ($str =~ /^(\d+)/); $str =~ s/^(\d+)//; $num = $match; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
2140
|
0
|
|
|
|
|
0
|
($match) = ($str =~ /^(\D+)/); $str =~ s/^(\D+)//; $unit = $match; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
2141
|
|
|
|
|
|
|
|
2142
|
|
|
|
|
|
|
#custom support for "weeks": |
2143
|
0
|
0
|
0
|
|
|
0
|
if($unit eq 'w' || $unit eq 'week' || $unit eq 'weeks' || $unit eq 'wk' || $unit eq 'wks') { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2144
|
0
|
|
|
|
|
0
|
$unit = 'days'; |
2145
|
0
|
|
|
|
|
0
|
$num = $num * 7; |
2146
|
|
|
|
|
|
|
} |
2147
|
|
|
|
|
|
|
|
2148
|
|
|
|
|
|
|
#custom support for "quarters": |
2149
|
0
|
0
|
0
|
|
|
0
|
if($unit eq 'q' || $unit eq 'quarter' || $unit eq 'quarters' || $unit eq 'qtr' || $unit eq 'qtrs') { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2150
|
0
|
|
|
|
|
0
|
$unit = 'months'; |
2151
|
0
|
|
|
|
|
0
|
$num = $num * 3; |
2152
|
|
|
|
|
|
|
} |
2153
|
|
|
|
|
|
|
|
2154
|
0
|
0
|
0
|
|
|
0
|
push @parts, { num => $num, unit => $unit } if ($num && $unit); |
2155
|
|
|
|
|
|
|
} |
2156
|
|
|
|
|
|
|
|
2157
|
0
|
0
|
|
|
|
0
|
return $v unless (@parts > 0); |
2158
|
|
|
|
|
|
|
|
2159
|
0
|
0
|
|
|
|
0
|
my $method = ($sign eq '-') ? 'subtract' : 'add'; |
2160
|
|
|
|
|
|
|
|
2161
|
0
|
|
|
|
|
0
|
my $map = $self->inflate_multifilter_date_unit_map; |
2162
|
0
|
|
|
|
|
0
|
my $count = 0; |
2163
|
0
|
|
|
|
|
0
|
foreach my $part (@parts) { |
2164
|
0
|
0
|
|
|
|
0
|
my $interval = $map->{$part->{unit}} or next; |
2165
|
0
|
0
|
|
|
|
0
|
my $newDt = $dt->$method( $interval => $part->{num} ) or next; |
2166
|
0
|
|
|
|
|
0
|
$count++; |
2167
|
0
|
|
|
|
|
0
|
$dt = $newDt; |
2168
|
|
|
|
|
|
|
} |
2169
|
|
|
|
|
|
|
|
2170
|
0
|
0
|
|
|
|
0
|
return $orig_v unless ($count); |
2171
|
|
|
|
|
|
|
|
2172
|
0
|
|
|
|
|
0
|
return $dt->ymd . ' ' . $dt->hms; |
2173
|
|
|
|
|
|
|
} |
2174
|
|
|
|
|
|
|
|
2175
|
|
|
|
|
|
|
# Equiv to Ext.ux.RapidApp.Plugin.RelativeDateTime.unitMap |
2176
|
|
|
|
|
|
|
has 'inflate_multifilter_date_unit_map', is => 'ro', default => sub {{ |
2177
|
|
|
|
|
|
|
|
2178
|
|
|
|
|
|
|
y => 'years', |
2179
|
|
|
|
|
|
|
year => 'years', |
2180
|
|
|
|
|
|
|
years => 'years', |
2181
|
|
|
|
|
|
|
yr => 'years', |
2182
|
|
|
|
|
|
|
yrs => 'years', |
2183
|
|
|
|
|
|
|
|
2184
|
|
|
|
|
|
|
m => 'months', |
2185
|
|
|
|
|
|
|
mo => 'months', |
2186
|
|
|
|
|
|
|
month => 'months', |
2187
|
|
|
|
|
|
|
months => 'months', |
2188
|
|
|
|
|
|
|
|
2189
|
|
|
|
|
|
|
d => 'days', |
2190
|
|
|
|
|
|
|
day => 'days', |
2191
|
|
|
|
|
|
|
days => 'days', |
2192
|
|
|
|
|
|
|
dy => 'days', |
2193
|
|
|
|
|
|
|
dys => 'days', |
2194
|
|
|
|
|
|
|
|
2195
|
|
|
|
|
|
|
h => 'hours', |
2196
|
|
|
|
|
|
|
hour => 'hours', |
2197
|
|
|
|
|
|
|
hours => 'hours', |
2198
|
|
|
|
|
|
|
hr => 'hours', |
2199
|
|
|
|
|
|
|
hrs => 'hours', |
2200
|
|
|
|
|
|
|
|
2201
|
|
|
|
|
|
|
i => 'minutes', |
2202
|
|
|
|
|
|
|
mi => 'minutes', |
2203
|
|
|
|
|
|
|
min => 'minutes', |
2204
|
|
|
|
|
|
|
mins => 'minutes', |
2205
|
|
|
|
|
|
|
minute => 'minutes', |
2206
|
|
|
|
|
|
|
minutes => 'minutes', |
2207
|
|
|
|
|
|
|
|
2208
|
|
|
|
|
|
|
s => 'seconds', |
2209
|
|
|
|
|
|
|
sec => 'seconds', |
2210
|
|
|
|
|
|
|
secs => 'seconds', |
2211
|
|
|
|
|
|
|
second => 'seconds', |
2212
|
|
|
|
|
|
|
second => 'seconds' |
2213
|
|
|
|
|
|
|
}}; |
2214
|
|
|
|
|
|
|
|
2215
|
|
|
|
|
|
|
has 'is_virtual_source', is => 'ro', lazy => 1, default => sub { |
2216
|
|
|
|
|
|
|
my $self = shift; |
2217
|
|
|
|
|
|
|
return ( |
2218
|
|
|
|
|
|
|
$self->ResultClass->result_source_instance->can('is_virtual') && |
2219
|
|
|
|
|
|
|
$self->ResultClass->result_source_instance->is_virtual |
2220
|
|
|
|
|
|
|
); |
2221
|
|
|
|
|
|
|
}, isa => 'Bool'; |
2222
|
|
|
|
|
|
|
|
2223
|
|
|
|
|
|
|
has 'DataStore_build_params' => ( is => 'ro', isa => 'HashRef', default => sub {{}} ); |
2224
|
|
|
|
|
|
|
before DataStore2_BUILD => sub { |
2225
|
|
|
|
|
|
|
my $self = shift; |
2226
|
|
|
|
|
|
|
|
2227
|
|
|
|
|
|
|
my @store_fields = map {{ name => $_ }} uniq( |
2228
|
|
|
|
|
|
|
$self->TableSpec->updated_column_order, |
2229
|
|
|
|
|
|
|
'loadContentCnf', #<-- specific to AppGrid2 |
2230
|
|
|
|
|
|
|
$self->record_pk |
2231
|
|
|
|
|
|
|
); |
2232
|
|
|
|
|
|
|
|
2233
|
|
|
|
|
|
|
my $store_params = { |
2234
|
|
|
|
|
|
|
store_autoLoad => 1, |
2235
|
|
|
|
|
|
|
reload_on_save => $self->reload_on_save, |
2236
|
|
|
|
|
|
|
remoteSort => \1, |
2237
|
|
|
|
|
|
|
store_fields => \@store_fields |
2238
|
|
|
|
|
|
|
}; |
2239
|
|
|
|
|
|
|
|
2240
|
|
|
|
|
|
|
$store_params->{create_handler} = RapidApp::Handler->new( scope => $self, method => '_dbiclink_create_records' ) if ( |
2241
|
|
|
|
|
|
|
defined $self->creatable_colspec and |
2242
|
|
|
|
|
|
|
not $self->can('create_records') |
2243
|
|
|
|
|
|
|
); |
2244
|
|
|
|
|
|
|
|
2245
|
|
|
|
|
|
|
$store_params->{update_handler} = RapidApp::Handler->new( scope => $self, method => '_dbiclink_update_records' ) if ( |
2246
|
|
|
|
|
|
|
defined $self->updatable_colspec and |
2247
|
|
|
|
|
|
|
not $self->can('update_records') |
2248
|
|
|
|
|
|
|
); |
2249
|
|
|
|
|
|
|
|
2250
|
|
|
|
|
|
|
$store_params->{destroy_handler} = RapidApp::Handler->new( scope => $self, method => '_dbiclink_destroy_records' ) if ( |
2251
|
|
|
|
|
|
|
defined $self->destroyable_relspec and |
2252
|
|
|
|
|
|
|
not $self->can('destroy_records') |
2253
|
|
|
|
|
|
|
); |
2254
|
|
|
|
|
|
|
|
2255
|
|
|
|
|
|
|
# New: Override to globally disable create/destroy for virtual sources: |
2256
|
|
|
|
|
|
|
if($self->is_virtual_source) { |
2257
|
|
|
|
|
|
|
exists $store_params->{create_handler} && delete $store_params->{create_handler}; |
2258
|
|
|
|
|
|
|
exists $store_params->{destroy_handler} && delete $store_params->{destroy_handler}; |
2259
|
|
|
|
|
|
|
$self->apply_flags( can_create => 0 ); |
2260
|
|
|
|
|
|
|
$self->apply_flags( can_destroy => 0 ); |
2261
|
|
|
|
|
|
|
} |
2262
|
|
|
|
|
|
|
|
2263
|
|
|
|
|
|
|
# merge this way to make sure the opts get set, but yet still allow |
2264
|
|
|
|
|
|
|
# the opts to be specifically overridden DataStore_build_params attr |
2265
|
|
|
|
|
|
|
# is defined but with different params |
2266
|
|
|
|
|
|
|
%{$self->DataStore_build_params} = ( %$store_params, %{$self->DataStore_build_params} ); |
2267
|
|
|
|
|
|
|
}; |
2268
|
|
|
|
|
|
|
|
2269
|
|
|
|
|
|
|
|
2270
|
|
|
|
|
|
|
|
2271
|
|
|
|
|
|
|
# convenience method: prints the primary keys of a Row object |
2272
|
|
|
|
|
|
|
# just used to print info to the screen during CRUD ops below |
2273
|
|
|
|
|
|
|
sub get_Row_Rs_label { |
2274
|
3
|
|
|
3
|
0
|
9
|
my $self = shift; |
2275
|
3
|
|
|
|
|
7
|
my $Row = shift; |
2276
|
3
|
|
|
|
|
8
|
my $verbose = shift; |
2277
|
|
|
|
|
|
|
|
2278
|
3
|
50
|
|
|
|
25
|
if($Row->isa('DBIx::Class::ResultSet')) { |
2279
|
0
|
|
|
|
|
0
|
my $Rs = $Row; |
2280
|
0
|
|
|
|
|
0
|
my $str = ref($Rs) . ' [' . $Rs->count . ' rows]'; |
2281
|
0
|
0
|
|
|
|
0
|
return $str unless ($verbose); |
2282
|
0
|
|
|
|
|
0
|
$str .= ':'; |
2283
|
0
|
|
|
|
|
0
|
$str .= "\n " . $self->get_Row_Rs_label($_) for ($Rs->all); |
2284
|
0
|
|
|
|
|
0
|
return $str; |
2285
|
|
|
|
|
|
|
} |
2286
|
|
|
|
|
|
|
|
2287
|
3
|
|
|
|
|
15
|
my $Source = $Row->result_source; |
2288
|
3
|
|
|
|
|
32
|
my @keys = $Source->primary_columns; |
2289
|
3
|
|
|
|
|
33
|
my $data = { $Row->get_columns }; |
2290
|
|
|
|
|
|
|
|
2291
|
3
|
|
|
|
|
15
|
my $str = ref($Row) . ' [ '; |
2292
|
3
|
|
|
|
|
25
|
$str .= $_ . ': ' . $data->{$_} . ' ' for (@keys); |
2293
|
3
|
|
|
|
|
11
|
$str .= ']'; |
2294
|
|
|
|
|
|
|
|
2295
|
3
|
|
|
|
|
16
|
return $str; |
2296
|
|
|
|
|
|
|
} |
2297
|
|
|
|
|
|
|
|
2298
|
|
|
|
|
|
|
# Gets programatically added as a method named 'update_records' (see BUILD modifier method above) |
2299
|
|
|
|
|
|
|
# |
2300
|
|
|
|
|
|
|
# This first runs updates on each supplied (and allowed) relation. |
2301
|
|
|
|
|
|
|
# It then re-runs a read_records to tell the client what the new values are. |
2302
|
|
|
|
|
|
|
# |
2303
|
|
|
|
|
|
|
sub _dbiclink_update_records { |
2304
|
3
|
|
|
3
|
|
10
|
my $self = shift; |
2305
|
3
|
|
|
|
|
8
|
my $params = shift; |
2306
|
|
|
|
|
|
|
|
2307
|
3
|
|
|
|
|
7
|
my $limit_columns; |
2308
|
3
|
|
|
|
|
14
|
my $declared_columns = $self->param_decodeIf($self->c->req->params->{columns}); |
2309
|
3
|
50
|
|
|
|
20
|
$limit_columns = { map {$_=>1} @$declared_columns } if ($declared_columns); |
|
19
|
|
|
|
|
86
|
|
2310
|
|
|
|
|
|
|
|
2311
|
|
|
|
|
|
|
# -- current real/valid columns according to DataStore2: |
2312
|
3
|
|
|
|
|
44
|
my %cols_indx = map {$_=>1} $self->column_name_list; |
|
39
|
|
|
|
|
97
|
|
2313
|
|
|
|
|
|
|
# -- |
2314
|
|
|
|
|
|
|
|
2315
|
3
|
|
|
|
|
13
|
my $arr = $params; |
2316
|
3
|
50
|
|
|
|
17
|
$arr = [ $params ] if (ref($params) eq 'HASH'); |
2317
|
|
|
|
|
|
|
|
2318
|
|
|
|
|
|
|
#my $Rs = $self->ResultSource->resultset; |
2319
|
3
|
|
|
|
|
28
|
my $Rs = $self->baseResultSet; |
2320
|
|
|
|
|
|
|
|
2321
|
3
|
|
|
|
|
1207
|
my @updated_keyvals = (); |
2322
|
3
|
|
|
|
|
15
|
my %keyval_changes = (); |
2323
|
|
|
|
|
|
|
|
2324
|
|
|
|
|
|
|
# FIXME!! |
2325
|
|
|
|
|
|
|
# There is a logic problem with update. The comparisons are done iteratively, and so when |
2326
|
|
|
|
|
|
|
# update is called on one row, and then the backend logic changes another row that is |
2327
|
|
|
|
|
|
|
# encountered later on in the update process, it can appear that rows were changed, when in fact they |
2328
|
|
|
|
|
|
|
# were the original values, and it can change the data in an inconsistent/non-atomic way. |
2329
|
|
|
|
|
|
|
# would be good to find a way to do this just like in create. What really needs to happen is |
2330
|
|
|
|
|
|
|
# at least the column_data_alias remapping needs to be atomic (like create). |
2331
|
|
|
|
|
|
|
# this currently only breaks in edge-cases (and where an incorrect/non-sensible set of colspecs |
2332
|
|
|
|
|
|
|
# was supplied to begin with, but still needs to be FIXED). Needs to be thought about... |
2333
|
|
|
|
|
|
|
# -- ^^^ --- UPDATE: I believe that I have solved this problem by now pushing rows into |
2334
|
|
|
|
|
|
|
# a queue and then running updates at the end. Need to spend a bit more |
2335
|
|
|
|
|
|
|
# time thinking about it though, so I am not removing the above comment yet |
2336
|
|
|
|
|
|
|
|
2337
|
|
|
|
|
|
|
try { |
2338
|
|
|
|
|
|
|
$self->ResultSource->schema->txn_do(sub { |
2339
|
3
|
|
|
|
|
1587
|
foreach my $data (@$arr) { |
2340
|
3
|
|
|
|
|
116
|
my $pkVal= $data->{$self->record_pk}; |
2341
|
3
|
50
|
|
|
|
16
|
defined $pkVal or die ref($self)."->update_records: Record is missing primary key '".$self->record_pk."'"; |
2342
|
3
|
50
|
|
|
|
28
|
my $BaseRow = $Rs->search($self->record_pk_cond($pkVal))->next or die usererr "Failed to find row by record_pk: $pkVal"; |
2343
|
|
|
|
|
|
|
|
2344
|
|
|
|
|
|
|
# -- Filter out the supplied data packet according to the supplied 'columns' parameter |
2345
|
|
|
|
|
|
|
# if the client has supplied a column list, filter out fieldnames that aren't in it. |
2346
|
|
|
|
|
|
|
# The Ext store currently sends all of its configured store fields, including ones it never |
2347
|
|
|
|
|
|
|
# loaded from the database. If we don't do this filtering, those fields will appear to have |
2348
|
|
|
|
|
|
|
# changed. |
2349
|
|
|
|
|
|
|
# |
2350
|
|
|
|
|
|
|
# FIXME: handle this on the client/js side so these fields aren't submitted at all |
2351
|
3
|
50
|
|
|
|
9466
|
if($limit_columns) { |
2352
|
3
|
|
|
|
|
507
|
%$data = map { $_ => $data->{$_} } grep { $limit_columns->{$_} } keys %$data; |
|
3
|
|
|
|
|
19
|
|
|
6
|
|
|
|
|
18
|
|
2353
|
|
|
|
|
|
|
} |
2354
|
|
|
|
|
|
|
# -- |
2355
|
|
|
|
|
|
|
|
2356
|
3
|
50
|
|
|
|
12
|
my @columns = grep { $_ ne $self->record_pk && $_ ne 'loadContentCnf' } keys %$data; |
|
3
|
|
|
|
|
126
|
|
2357
|
|
|
|
|
|
|
|
2358
|
3
|
|
|
|
|
115
|
@columns = $self->TableSpec->filter_updatable_columns(@columns); |
2359
|
|
|
|
|
|
|
|
2360
|
|
|
|
|
|
|
# -- Limit to current real/valid columns according to DataStore2: |
2361
|
3
|
|
|
|
|
21
|
@columns = grep { $cols_indx{$_} } @columns; |
|
3
|
|
|
|
|
16
|
|
2362
|
|
|
|
|
|
|
# -- |
2363
|
|
|
|
|
|
|
|
2364
|
3
|
|
|
|
|
45
|
my @update_queue = $self->prepare_record_updates($BaseRow,\@columns,$data); |
2365
|
|
|
|
|
|
|
|
2366
|
|
|
|
|
|
|
# Update all the rows at the end: |
2367
|
3
|
|
|
|
|
29
|
$self->process_update_queue(@update_queue); |
2368
|
|
|
|
|
|
|
|
2369
|
|
|
|
|
|
|
# Get the new record_pk for the row (it probably hasn't changed, but it could have): |
2370
|
3
|
|
|
|
|
18
|
my $newPkVal = $self->generate_record_pk_value({ $BaseRow->get_columns }); |
2371
|
3
|
|
|
|
|
18
|
push @updated_keyvals, $newPkVal; |
2372
|
3
|
50
|
|
|
|
40
|
$keyval_changes{$newPkVal} = $pkVal unless ("$pkVal" eq "$newPkVal"); |
2373
|
|
|
|
|
|
|
} |
2374
|
3
|
|
|
3
|
|
243
|
}); |
2375
|
|
|
|
|
|
|
} |
2376
|
|
|
|
|
|
|
catch { |
2377
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
2378
|
0
|
|
|
|
|
0
|
$self->handle_dbic_exception($err); |
2379
|
|
|
|
|
|
|
#die usererr rawhtml $self->make_dbic_exception_friendly($err), title => 'Database Error'; |
2380
|
3
|
|
|
|
|
58
|
}; |
2381
|
|
|
|
|
|
|
|
2382
|
|
|
|
|
|
|
# -- |
2383
|
|
|
|
|
|
|
# Perform a fresh lookup of all the records we just updated and send them back to the client: |
2384
|
|
|
|
|
|
|
delete $self->c->req->params->{ $self->_rst_qry_param } if ( |
2385
|
|
|
|
|
|
|
# clear any existing rst_qry to prevent polluting the read |
2386
|
3
|
50
|
|
|
|
39431
|
exists $self->c->req->params->{ $self->_rst_qry_param } |
2387
|
|
|
|
|
|
|
); |
2388
|
|
|
|
|
|
|
my $newdata = $self->DataStore->read({ |
2389
|
3
|
|
|
|
|
131
|
columns => [ keys %{ $arr->[0] } ], |
|
3
|
|
|
|
|
54
|
|
2390
|
|
|
|
|
|
|
id_in => \@updated_keyvals |
2391
|
|
|
|
|
|
|
}); |
2392
|
|
|
|
|
|
|
# -- |
2393
|
|
|
|
|
|
|
|
2394
|
|
|
|
|
|
|
## ---------------- |
2395
|
|
|
|
|
|
|
# NEW: We need to make sure the order of the returned rows matches the supplied rows; |
2396
|
|
|
|
|
|
|
# Ext's data store uses the order rather than the record ids to match. If we don't do |
2397
|
|
|
|
|
|
|
# this it could mix up the rows and cause subsequent updates to change the wrong rows!! |
2398
|
|
|
|
|
|
|
{ |
2399
|
3
|
|
|
|
|
13
|
my %pkRowMap = map { $_->{$self->record_pk} => $_ } @{$newdata->{rows}}; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
106
|
|
|
3
|
|
|
|
|
13
|
|
2400
|
3
|
|
|
|
|
13
|
my $supplied_count = scalar @updated_keyvals; |
2401
|
3
|
|
|
|
|
13
|
my $returned_count = scalar keys %pkRowMap; |
2402
|
3
|
50
|
|
|
|
15
|
die "Supplied/returned row mismatch. Expected $supplied_count rows, got $returned_count. " |
2403
|
|
|
|
|
|
|
unless ($supplied_count == $returned_count); |
2404
|
|
|
|
|
|
|
|
2405
|
|
|
|
|
|
|
# Manually set the correct order |
2406
|
3
|
|
|
|
|
12
|
@{$newdata->{rows}} = map { $pkRowMap{$_} } @updated_keyvals; |
|
3
|
|
|
|
|
15
|
|
|
3
|
|
|
|
|
14
|
|
2407
|
|
|
|
|
|
|
} |
2408
|
|
|
|
|
|
|
## ---------------- |
2409
|
|
|
|
|
|
|
|
2410
|
|
|
|
|
|
|
|
2411
|
|
|
|
|
|
|
# -- Restore the original record_pk, if it changed, and put the new value in another key. |
2412
|
|
|
|
|
|
|
# This is needed to make sure the client can keep track of which row is which. Code in datastore-plus |
2413
|
|
|
|
|
|
|
# then detects this and updates the idProperty in the record to the new value so it will be used |
2414
|
|
|
|
|
|
|
# in subsequent requests. THIS APPLIES ONLY IF THE PRIMARY KEYS ARE EDITABLE, WHICH ONLY HAPPENS |
2415
|
|
|
|
|
|
|
# IN RARE SITUATIONS: |
2416
|
3
|
|
|
|
|
9
|
foreach my $row (@{$newdata->{rows}}) { |
|
3
|
|
|
|
|
12
|
|
2417
|
3
|
|
|
|
|
92
|
my $newPkVal = $row->{$self->record_pk}; |
2418
|
3
|
50
|
|
|
|
20
|
my $oldPkVal = $keyval_changes{$newPkVal} or next; |
2419
|
0
|
|
|
|
|
0
|
$row->{$self->record_pk . '_new'} = $row->{$self->record_pk}; |
2420
|
0
|
|
|
|
|
0
|
$row->{$self->record_pk} = $oldPkVal; |
2421
|
|
|
|
|
|
|
} |
2422
|
|
|
|
|
|
|
# -- |
2423
|
|
|
|
|
|
|
|
2424
|
|
|
|
|
|
|
return { |
2425
|
3
|
|
|
|
|
37
|
%$newdata, |
2426
|
|
|
|
|
|
|
success => \1, |
2427
|
|
|
|
|
|
|
msg => 'Update Succeeded' |
2428
|
|
|
|
|
|
|
}; |
2429
|
|
|
|
|
|
|
} |
2430
|
|
|
|
|
|
|
|
2431
|
|
|
|
|
|
|
sub process_update_queue { |
2432
|
3
|
|
|
3
|
0
|
9
|
my $self = shift; |
2433
|
3
|
|
|
|
|
9
|
my @update_queue = @_; |
2434
|
|
|
|
|
|
|
|
2435
|
3
|
|
|
|
|
21
|
my $lock_keys = $self->_get_rs_lock_keys; |
2436
|
3
|
50
|
|
|
|
357
|
my @excl = $lock_keys ? keys %$lock_keys : (); |
2437
|
|
|
|
|
|
|
|
2438
|
3
|
|
|
|
|
18
|
foreach my $upd (@update_queue) { |
2439
|
3
|
50
|
|
|
|
15
|
if(my $chg = $upd->{change}) { |
|
|
0
|
|
|
|
|
|
2440
|
|
|
|
|
|
|
# We simply exclude the lock_keys from the update instead of changing them, because |
2441
|
|
|
|
|
|
|
# this is safer. They should already be the same, but if they aren't, it is more |
2442
|
|
|
|
|
|
|
# likely taht the client has outdated data than the server is somehow wrong |
2443
|
3
|
|
0
|
|
|
12
|
exists $chg->{$_} and delete $chg->{$_} for (@excl); |
2444
|
3
|
|
|
|
|
30
|
$upd->{row}->update($chg); |
2445
|
|
|
|
|
|
|
} |
2446
|
|
|
|
|
|
|
elsif($upd->{rel_update}) { |
2447
|
|
|
|
|
|
|
# Special handling for updates to relationship columns |
2448
|
|
|
|
|
|
|
#(which aren't real columns): |
2449
|
0
|
|
|
|
|
0
|
$self->apply_virtual_rel_col_update($upd->{row},$upd->{rel_update}); |
2450
|
|
|
|
|
|
|
} |
2451
|
|
|
|
|
|
|
} |
2452
|
|
|
|
|
|
|
} |
2453
|
|
|
|
|
|
|
|
2454
|
|
|
|
|
|
|
# currently this just handles updates to m2m relationship columns, but, this is |
2455
|
|
|
|
|
|
|
# also where other arbitrary update logic could go for other kinds of virtual |
2456
|
|
|
|
|
|
|
# columns that may be added in the future |
2457
|
|
|
|
|
|
|
sub apply_virtual_rel_col_update { |
2458
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2459
|
0
|
|
|
|
|
0
|
my $UpdRow = shift; |
2460
|
0
|
|
|
|
|
0
|
my $update = shift; |
2461
|
|
|
|
|
|
|
|
2462
|
0
|
|
|
|
|
0
|
my $Source = $UpdRow->result_source; |
2463
|
|
|
|
|
|
|
|
2464
|
0
|
|
|
|
|
0
|
foreach my $colname (keys %$update) { |
2465
|
|
|
|
|
|
|
## currently ignore everything but m2m relationship columns: |
2466
|
0
|
0
|
|
|
|
0
|
my $info = $Source->relationship_info($colname) or next; |
2467
|
0
|
0
|
|
|
|
0
|
my $m2m_attrs = $info->{attrs}->{m2m_attrs} or next; |
2468
|
|
|
|
|
|
|
|
2469
|
|
|
|
|
|
|
# This method should have been setup by the call to "many_to_many": |
2470
|
0
|
|
|
|
|
0
|
my $method = 'set_' . $colname; |
2471
|
0
|
0
|
|
|
|
0
|
$UpdRow->can($method) or die "Row '" . ref($UpdRow) . |
2472
|
|
|
|
|
|
|
"' missing expected many_to_many method '$method' - cannot update m2m data for '$colname'!"; |
2473
|
|
|
|
|
|
|
|
2474
|
0
|
|
|
|
|
0
|
my @ids = split(/\s*,\s*/,$update->{$colname}); |
2475
|
|
|
|
|
|
|
|
2476
|
0
|
|
|
|
|
0
|
my $Rs = $Source->schema->source($m2m_attrs->{rrinfo}->{source})->resultset; |
2477
|
0
|
|
|
|
|
0
|
my $keycol = $m2m_attrs->{rrinfo}->{cond_info}->{foreign}; |
2478
|
|
|
|
|
|
|
|
2479
|
0
|
|
|
|
|
0
|
my @rrows = $self->_chain_search_rs($Rs,{ $keycol => { '-in' => \@ids }})->all; |
2480
|
0
|
|
|
|
|
0
|
my $count = scalar @rrows; |
2481
|
|
|
|
|
|
|
|
2482
|
0
|
0
|
|
|
|
0
|
scream_color(WHITE.ON_BLUE.BOLD," --> Setting '$colname' m2m links (count: $count)") |
2483
|
|
|
|
|
|
|
if($self->c->debug); |
2484
|
|
|
|
|
|
|
|
2485
|
0
|
|
|
|
|
0
|
$UpdRow->$method(\@rrows); |
2486
|
|
|
|
|
|
|
} |
2487
|
|
|
|
|
|
|
} |
2488
|
|
|
|
|
|
|
|
2489
|
|
|
|
|
|
|
|
2490
|
|
|
|
|
|
|
# moved/generalized out of _dbiclink_update_records to also be used by batch_update: |
2491
|
|
|
|
|
|
|
sub prepare_record_updates { |
2492
|
3
|
|
|
3
|
0
|
11
|
my $self = shift; |
2493
|
3
|
|
|
|
|
9
|
my $BaseRow = shift; |
2494
|
3
|
|
|
|
|
6
|
my $columns = shift; |
2495
|
3
|
|
|
|
|
8
|
my $data = shift; |
2496
|
3
|
|
|
|
|
7
|
my $ignore_current = shift; |
2497
|
|
|
|
|
|
|
|
2498
|
3
|
|
|
|
|
10
|
my @update_queue = (); |
2499
|
|
|
|
|
|
|
|
2500
|
|
|
|
|
|
|
$self->TableSpec->walk_columns_deep(sub { |
2501
|
3
|
|
|
3
|
|
8
|
my $TableSpec = shift; |
2502
|
3
|
|
|
|
|
11
|
my @columns = @_; |
2503
|
|
|
|
|
|
|
|
2504
|
3
|
|
33
|
|
|
28
|
my $Row = $_{return} || $BaseRow; |
2505
|
3
|
50
|
|
|
|
19
|
return ' ' if ($Row eq ' '); |
2506
|
|
|
|
|
|
|
|
2507
|
3
|
|
|
|
|
10
|
my $rel = $_{rel}; |
2508
|
3
|
50
|
|
|
|
11
|
my $UpdRow = $rel ? $Row->$rel : $Row; |
2509
|
|
|
|
|
|
|
|
2510
|
|
|
|
|
|
|
|
2511
|
|
|
|
|
|
|
# ---- New partial/preliminary auto create relationship support |
2512
|
|
|
|
|
|
|
# |
2513
|
|
|
|
|
|
|
# 1st-level relationships that don't already exist that are listed in the |
2514
|
|
|
|
|
|
|
# 'update_create_rels' attr will be automatically created (as blank so they |
2515
|
|
|
|
|
|
|
# can be updated in the subsequent update process) |
2516
|
|
|
|
|
|
|
# |
2517
|
|
|
|
|
|
|
# TODO: support any depth via an alternate 'update_create_relspec' attr and |
2518
|
|
|
|
|
|
|
# create with supplied column values instead of blank (1 step instead of 2) |
2519
|
|
|
|
|
|
|
# |
2520
|
3
|
|
|
|
|
10
|
my %ucrls = map {$_=>1} @{$self->update_create_rels}; |
|
0
|
|
|
|
|
0
|
|
|
3
|
|
|
|
|
129
|
|
2521
|
3
|
0
|
33
|
|
|
16
|
if($rel && !$UpdRow && $ucrls{$rel} && $_{depth} == 1){ |
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
2522
|
0
|
|
|
|
|
0
|
$UpdRow = $Row->create_related($rel,{})->get_from_storage; |
2523
|
0
|
|
|
|
|
0
|
my $msg = 'Auto CREATED RELATED -> ' . $self->get_Row_Rs_label($UpdRow) . "\n"; |
2524
|
0
|
0
|
|
|
|
0
|
scream_color(WHITE.ON_GREEN.BOLD,$msg) if($self->c->debug); |
2525
|
|
|
|
|
|
|
} |
2526
|
|
|
|
|
|
|
# |
2527
|
|
|
|
|
|
|
# ---- |
2528
|
|
|
|
|
|
|
|
2529
|
|
|
|
|
|
|
|
2530
|
3
|
|
|
|
|
10
|
my %update = map { $_ => $data->{ $_{name_map}->{$_} } } keys %{$_{name_map}}; |
|
3
|
|
|
|
|
16
|
|
|
3
|
|
|
|
|
16
|
|
2531
|
|
|
|
|
|
|
|
2532
|
|
|
|
|
|
|
# --- Need to do a map and a grep here; map to remap the values, and grep to prevent |
2533
|
|
|
|
|
|
|
# the new values from being clobbered by identical key names from the original data: |
2534
|
3
|
|
|
|
|
12
|
my $alias = { %{ $TableSpec->column_data_alias } }; |
|
3
|
|
|
|
|
106
|
|
2535
|
|
|
|
|
|
|
# -- strip out aliases that are identical to the original value. This will happen in the special |
2536
|
|
|
|
|
|
|
# case of an update to a rel col that is ALSO a local col when 'priority_rel_columns' is on. |
2537
|
|
|
|
|
|
|
# It shouldn't happen other times, but if it does, this is the right way to handle it, regardless: |
2538
|
3
|
|
0
|
|
|
14
|
$_ eq $alias->{$_} and delete $alias->{$_} for (keys %$alias); |
2539
|
|
|
|
|
|
|
# -- |
2540
|
3
|
|
|
|
|
12
|
my %revalias = map {$_=>1} values %$alias; |
|
0
|
|
|
|
|
0
|
|
2541
|
3
|
50
|
|
|
|
10
|
%update = map { $alias->{$_} ? $alias->{$_} : $_ => $update{$_} } grep { !$revalias{$_} } keys %update; |
|
3
|
|
|
|
|
22
|
|
|
3
|
|
|
|
|
15
|
|
2542
|
|
|
|
|
|
|
# --- |
2543
|
|
|
|
|
|
|
|
2544
|
3
|
50
|
|
|
|
14
|
unless (defined $UpdRow) { |
2545
|
0
|
0
|
|
|
|
0
|
scream('NOTICE: Relationship/row "' . $rel . '" is not defined',\@columns) |
2546
|
|
|
|
|
|
|
if($self->c->debug); |
2547
|
|
|
|
|
|
|
|
2548
|
|
|
|
|
|
|
# New: Throw an error when trying to update a column through a missing relationship so |
2549
|
|
|
|
|
|
|
# the user knows instead of silenting ignoring those columns. |
2550
|
|
|
|
|
|
|
# TODO: make this an option and alternatively *create* the missing relationship based on |
2551
|
|
|
|
|
|
|
# settings of the relationship (needs an API/design to be thought up) |
2552
|
0
|
0
|
|
|
|
0
|
if($rel) { |
2553
|
0
|
|
|
|
|
0
|
my $relf = '<span style="font-weight:bold;color:navy;">' . $rel . '</span>'; |
2554
|
0
|
|
|
|
|
0
|
my $cols = '<span style="font-family:monospace;font-size:.85em;">' . join(', ',keys %update) . '</span>'; |
2555
|
0
|
|
|
|
|
0
|
my $html = '<span style="font-size:1.3em;">' . |
2556
|
|
|
|
|
|
|
"Cannot update related field(s) of $relf ($cols) because there is no $relf set for this record. " . |
2557
|
|
|
|
|
|
|
"<br><br>This probably just means you need to add or select a $relf first.</span>"; |
2558
|
0
|
|
|
|
|
0
|
die usererr rawhtml $html, title => "Can't update fields of non-existant related '$rel' "; |
2559
|
|
|
|
|
|
|
} |
2560
|
|
|
|
|
|
|
} |
2561
|
|
|
|
|
|
|
|
2562
|
|
|
|
|
|
|
# This should throw an error to the user, too: |
2563
|
3
|
50
|
|
|
|
39
|
if ($UpdRow->isa('DBIx::Class::ResultSet')) { |
2564
|
0
|
0
|
|
|
|
0
|
scream('NOTICE: Skipping multi relationship "' . $rel . '"') |
2565
|
|
|
|
|
|
|
if($self->c->debug); |
2566
|
0
|
|
|
|
|
0
|
return ' '; |
2567
|
|
|
|
|
|
|
} |
2568
|
|
|
|
|
|
|
|
2569
|
|
|
|
|
|
|
|
2570
|
|
|
|
|
|
|
# --- pull out updates to virtual relationship columns |
2571
|
3
|
|
|
|
|
22
|
my $Source = $UpdRow->result_source; |
2572
|
3
|
|
|
|
|
35
|
my $relcol_updates = {}; |
2573
|
|
|
|
|
|
|
(!$Source->has_column($_) && $Source->has_relationship($_)) and |
2574
|
3
|
|
66
|
|
|
27
|
$relcol_updates->{$_} = delete $update{$_} for (keys %update); |
|
|
|
33
|
|
|
|
|
2575
|
|
|
|
|
|
|
# add to the update queue with a special attr 'rel_update' instead of 'change' |
2576
|
3
|
50
|
|
|
|
75
|
push @update_queue,{ row => $UpdRow, rel_update => $relcol_updates } |
2577
|
|
|
|
|
|
|
if (keys %$relcol_updates > 0); |
2578
|
|
|
|
|
|
|
# --- |
2579
|
|
|
|
|
|
|
|
2580
|
3
|
|
|
|
|
13
|
my $change = \%update; |
2581
|
|
|
|
|
|
|
|
2582
|
3
|
50
|
|
|
|
16
|
unless($ignore_current) { |
2583
|
|
|
|
|
|
|
|
2584
|
3
|
|
|
|
|
66
|
my %current = $UpdRow->get_columns; |
2585
|
|
|
|
|
|
|
|
2586
|
3
|
|
|
|
|
15
|
$change = {}; |
2587
|
3
|
|
|
|
|
12
|
foreach my $col (keys %update) { |
2588
|
5
|
|
|
5
|
|
74
|
no warnings 'uninitialized'; |
|
5
|
|
|
|
|
15
|
|
|
5
|
|
|
|
|
13119
|
|
2589
|
3
|
50
|
|
|
|
19
|
next unless (exists $current{$col}); |
2590
|
3
|
0
|
33
|
|
|
14
|
next if (! defined $update{$col} and ! defined $current{$col}); |
2591
|
3
|
50
|
|
|
|
12
|
next if ($update{$col} eq $current{$col}); |
2592
|
3
|
|
|
|
|
13
|
$change->{$col} = $update{$col}; |
2593
|
|
|
|
|
|
|
} |
2594
|
|
|
|
|
|
|
|
2595
|
3
|
|
|
|
|
28
|
my $msg = 'Will UPDATE -> ' . $self->get_Row_Rs_label($UpdRow) . "\n"; |
2596
|
3
|
50
|
|
|
|
17
|
if (keys %$change > 0){ |
2597
|
3
|
|
|
|
|
43
|
my $t = Text::TabularDisplay->new(qw(column old new)); |
2598
|
3
|
|
|
|
|
467
|
$t->add($_,print_trunc(60,$current{$_}),print_trunc(60,$change->{$_})) for (keys %$change); |
2599
|
3
|
|
|
|
|
267
|
$msg .= $t->render; |
2600
|
|
|
|
|
|
|
} |
2601
|
|
|
|
|
|
|
else { |
2602
|
0
|
|
|
|
|
0
|
$msg .= 'No Changes'; |
2603
|
|
|
|
|
|
|
} |
2604
|
3
|
50
|
|
|
|
565
|
scream_color(WHITE.ON_BLUE.BOLD,$msg) if($self->c->debug); |
2605
|
|
|
|
|
|
|
} |
2606
|
|
|
|
|
|
|
|
2607
|
3
|
|
|
|
|
32
|
push @update_queue,{ row => $UpdRow, change => $change }; |
2608
|
|
|
|
|
|
|
|
2609
|
3
|
|
|
|
|
26
|
return $UpdRow; |
2610
|
3
|
|
|
|
|
153
|
},@$columns); |
2611
|
|
|
|
|
|
|
|
2612
|
3
|
|
|
|
|
98
|
return @update_queue; |
2613
|
|
|
|
|
|
|
} |
2614
|
|
|
|
|
|
|
|
2615
|
|
|
|
|
|
|
# Works with the hashtree supplied to create_records to recursively |
2616
|
|
|
|
|
|
|
# remap columns according to supplied TableSpec column_data_aliases |
2617
|
|
|
|
|
|
|
sub hashtree_col_alias_map_deep { |
2618
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2619
|
0
|
|
|
|
|
0
|
my $hash = shift; |
2620
|
0
|
|
|
|
|
0
|
my $TableSpec = shift; |
2621
|
|
|
|
|
|
|
|
2622
|
|
|
|
|
|
|
# Recursive: |
2623
|
0
|
|
|
|
|
0
|
foreach my $rel (grep { ref($hash->{$_}) eq 'HASH' } keys %$hash) { |
|
0
|
|
|
|
|
0
|
|
2624
|
0
|
0
|
|
|
|
0
|
my $rel_TableSpec = $TableSpec->related_TableSpec->{$rel} or next; |
2625
|
0
|
|
|
|
|
0
|
$hash->{$rel} = $self->hashtree_col_alias_map_deep($hash->{$rel},$rel_TableSpec); |
2626
|
|
|
|
|
|
|
} |
2627
|
|
|
|
|
|
|
|
2628
|
|
|
|
|
|
|
# -- Need to do a map and a grep here; map to remap the values, and grep to prevent |
2629
|
|
|
|
|
|
|
# the new values from being clobbered by identical key names from the original data: |
2630
|
0
|
|
|
|
|
0
|
my $alias = $TableSpec->column_data_alias; |
2631
|
0
|
|
|
|
|
0
|
my %revalias = map {$_=>1} grep {!exists $hash->{$_}} values %$alias; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
2632
|
0
|
0
|
|
|
|
0
|
%$hash = map { $alias->{$_} ? $alias->{$_} : $_ => $hash->{$_} } grep { !$revalias{$_} } keys %$hash; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
2633
|
|
|
|
|
|
|
# -- |
2634
|
|
|
|
|
|
|
|
2635
|
|
|
|
|
|
|
# --- remap special m2m relationship column values: |
2636
|
|
|
|
|
|
|
# see apply_virtual_rel_col_update() above for the 'update' version |
2637
|
0
|
|
|
|
|
0
|
my $Source = $TableSpec->ResultSource; |
2638
|
0
|
|
|
|
|
0
|
foreach my $col (keys %$hash) { |
2639
|
0
|
0
|
|
|
|
0
|
next if ($Source->has_column($col)); |
2640
|
0
|
0
|
|
|
|
0
|
my $info = $Source->relationship_info($col) or next; |
2641
|
0
|
0
|
|
|
|
0
|
my $m2m_attrs = $info->{attrs}->{m2m_attrs} or next; |
2642
|
0
|
|
|
|
|
0
|
my $keycol = $m2m_attrs->{rrinfo}->{cond_info}->{self}; |
2643
|
|
|
|
|
|
|
|
2644
|
0
|
|
|
|
|
0
|
my @ids = split(/\s*,\s*/,$hash->{$col}); |
2645
|
|
|
|
|
|
|
|
2646
|
|
|
|
|
|
|
# Convert the value into a valid "has_many" create packet: |
2647
|
0
|
|
|
|
|
0
|
$hash->{$col} = [ map { { $keycol => $_ } } @ids ]; |
|
0
|
|
|
|
|
0
|
|
2648
|
|
|
|
|
|
|
} |
2649
|
|
|
|
|
|
|
# --- |
2650
|
|
|
|
|
|
|
|
2651
|
0
|
|
|
|
|
0
|
return $hash; |
2652
|
|
|
|
|
|
|
} |
2653
|
|
|
|
|
|
|
|
2654
|
|
|
|
|
|
|
|
2655
|
|
|
|
|
|
|
# Gets programatically added as a method named 'create_records' (see BUILD modifier method above) |
2656
|
|
|
|
|
|
|
sub _dbiclink_create_records { |
2657
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
2658
|
0
|
|
|
|
|
0
|
my $params = shift; |
2659
|
|
|
|
|
|
|
|
2660
|
0
|
|
|
|
|
0
|
my $arr = $params; |
2661
|
0
|
0
|
|
|
|
0
|
$arr = [ $params ] if (ref($params) eq 'HASH'); |
2662
|
|
|
|
|
|
|
|
2663
|
|
|
|
|
|
|
#my $Rs = $self->ResultSource->resultset; |
2664
|
0
|
|
|
|
|
0
|
my $Rs = $self->baseResultSet; |
2665
|
|
|
|
|
|
|
|
2666
|
|
|
|
|
|
|
# create_columns turned off in 080-DataStore.js - 2014-11-24 by HV |
2667
|
|
|
|
|
|
|
#my @req_columns = $self->get_req_columns(undef,'create_columns'); |
2668
|
0
|
|
|
|
|
0
|
my @req_columns = $self->get_req_columns; |
2669
|
|
|
|
|
|
|
|
2670
|
|
|
|
|
|
|
# -- current real/valid columns according to DataStore2: |
2671
|
0
|
|
|
|
|
0
|
my %cols_indx = map {$_=>1} $self->column_name_list; |
|
0
|
|
|
|
|
0
|
|
2672
|
|
|
|
|
|
|
# -- |
2673
|
|
|
|
|
|
|
|
2674
|
0
|
|
|
|
|
0
|
my @updated_keyvals = (); |
2675
|
|
|
|
|
|
|
|
2676
|
|
|
|
|
|
|
try { |
2677
|
|
|
|
|
|
|
$self->ResultSource->schema->txn_do(sub { |
2678
|
0
|
|
|
|
|
0
|
foreach my $data (@$arr) { |
2679
|
|
|
|
|
|
|
|
2680
|
|
|
|
|
|
|
# Apply optional base/hard coded data: |
2681
|
0
|
|
|
|
|
0
|
%$data = ( %$data, %{$self->_CreateData} ); |
|
0
|
|
|
|
|
0
|
|
2682
|
0
|
|
|
|
|
0
|
my @columns = uniq(keys %$data,@req_columns); |
2683
|
0
|
0
|
|
|
|
0
|
@columns = grep { $_ ne $self->record_pk && $_ ne 'loadContentCnf' } @columns; |
|
0
|
|
|
|
|
0
|
|
2684
|
0
|
|
|
|
|
0
|
@columns = $self->TableSpec->filter_creatable_columns(@columns); |
2685
|
|
|
|
|
|
|
|
2686
|
|
|
|
|
|
|
# -- Limit to current real/valid columns according to DataStore2: |
2687
|
0
|
|
|
|
|
0
|
@columns = grep { $cols_indx{$_} } @columns; |
|
0
|
|
|
|
|
0
|
|
2688
|
|
|
|
|
|
|
# -- |
2689
|
|
|
|
|
|
|
|
2690
|
0
|
|
|
|
|
0
|
my $relspecs = $self->TableSpec->columns_to_relspec_map(@columns); |
2691
|
|
|
|
|
|
|
|
2692
|
0
|
|
|
|
|
0
|
my $create_hash = {}; |
2693
|
|
|
|
|
|
|
|
2694
|
0
|
|
|
|
|
0
|
foreach my $rel (keys %$relspecs) { |
2695
|
0
|
0
|
|
|
|
0
|
$create_hash->{$rel} = {} unless (defined $create_hash->{$rel}); |
2696
|
|
|
|
|
|
|
exists $data->{$_->{orig_colname}} and $create_hash->{$rel}->{$_->{local_colname}} = $data->{$_->{orig_colname}} |
2697
|
0
|
|
0
|
|
|
0
|
for (@{$relspecs->{$rel}}); |
|
0
|
|
|
|
|
0
|
|
2698
|
|
|
|
|
|
|
} |
2699
|
|
|
|
|
|
|
|
2700
|
0
|
|
0
|
|
|
0
|
my $create = delete $create_hash->{''} || {}; |
2701
|
0
|
|
|
|
|
0
|
$create = { %$create_hash, %$create }; |
2702
|
|
|
|
|
|
|
|
2703
|
|
|
|
|
|
|
# -- Recursively remap column_data_alias: |
2704
|
0
|
|
|
|
|
0
|
$create = $self->hashtree_col_alias_map_deep($create,$self->TableSpec); |
2705
|
|
|
|
|
|
|
# -- |
2706
|
|
|
|
|
|
|
|
2707
|
0
|
|
|
|
|
0
|
my $msg = 'CREATE -> ' . ref($Rs) . "\n"; |
2708
|
0
|
0
|
|
|
|
0
|
if (keys %$create > 0){ |
2709
|
0
|
|
|
|
|
0
|
my $t = Text::TabularDisplay->new(qw(column value)); |
2710
|
|
|
|
|
|
|
#$t->add($_,ref $create->{$_} ? Dumper($create->{$_}) : $create->{$_} ) for (keys %$create); |
2711
|
|
|
|
|
|
|
#$t->add($_,disp(sub{ ref $_ ? Dumper($_) : undef },$create->{$_}) ) for (keys %$create); |
2712
|
0
|
|
|
|
|
0
|
$t->add($_,print_trunc(60,$create->{$_})) for (keys %$create); |
2713
|
0
|
|
|
|
|
0
|
$msg .= $t->render; |
2714
|
|
|
|
|
|
|
} |
2715
|
|
|
|
|
|
|
else { |
2716
|
0
|
|
|
|
|
0
|
$msg .= 'Empty Record'; |
2717
|
|
|
|
|
|
|
} |
2718
|
0
|
0
|
|
|
|
0
|
scream_color(WHITE.ON_GREEN.BOLD,$msg) if($self->c->debug); |
2719
|
0
|
|
|
|
|
0
|
my $Row = $Rs->create($create); |
2720
|
|
|
|
|
|
|
|
2721
|
0
|
|
|
|
|
0
|
push @updated_keyvals, $self->generate_record_pk_value({ $Row->get_columns }); |
2722
|
|
|
|
|
|
|
} |
2723
|
0
|
|
|
0
|
|
0
|
}); |
2724
|
|
|
|
|
|
|
} |
2725
|
|
|
|
|
|
|
catch { |
2726
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
2727
|
0
|
|
|
|
|
0
|
$self->handle_dbic_exception($err); |
2728
|
|
|
|
|
|
|
#die usererr rawhtml $self->make_dbic_exception_friendly($err), title => 'Database Error'; |
2729
|
0
|
|
|
|
|
0
|
}; |
2730
|
|
|
|
|
|
|
|
2731
|
|
|
|
|
|
|
# -- |
2732
|
|
|
|
|
|
|
# Perform a fresh lookup of all the records we just updated and send them back to the client: |
2733
|
|
|
|
|
|
|
delete $self->c->req->params->{ $self->_rst_qry_param } if ( |
2734
|
|
|
|
|
|
|
# clear any existing rst_qry to prevent polluting the read |
2735
|
0
|
0
|
|
|
|
0
|
exists $self->c->req->params->{ $self->_rst_qry_param } |
2736
|
|
|
|
|
|
|
); |
2737
|
0
|
|
|
|
|
0
|
my $newdata = $self->DataStore->read({ |
2738
|
|
|
|
|
|
|
columns => \@req_columns, |
2739
|
|
|
|
|
|
|
id_in => \@updated_keyvals |
2740
|
|
|
|
|
|
|
}); |
2741
|
|
|
|
|
|
|
# -- |
2742
|
|
|
|
|
|
|
|
2743
|
|
|
|
|
|
|
die usererr rawhtml "Unknown error; no records were created", |
2744
|
0
|
0
|
0
|
|
|
0
|
title => 'Create Failed' unless ($newdata && $newdata->{results}); |
2745
|
|
|
|
|
|
|
|
2746
|
|
|
|
|
|
|
return { |
2747
|
0
|
|
|
|
|
0
|
%$newdata, |
2748
|
|
|
|
|
|
|
success => \1, |
2749
|
|
|
|
|
|
|
msg => 'Create Succeeded', |
2750
|
|
|
|
|
|
|
use_this => 1 |
2751
|
|
|
|
|
|
|
}; |
2752
|
|
|
|
|
|
|
} |
2753
|
|
|
|
|
|
|
|
2754
|
|
|
|
|
|
|
# Gets programatically added as a method named 'destroy_records' (see BUILD modifier method above) |
2755
|
|
|
|
|
|
|
sub _dbiclink_destroy_records { |
2756
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
2757
|
0
|
|
|
|
|
0
|
my $params = shift; |
2758
|
|
|
|
|
|
|
|
2759
|
0
|
|
|
|
|
0
|
my $arr = $params; |
2760
|
0
|
0
|
|
|
|
0
|
$arr = [ $params ] if (not ref($params)); |
2761
|
|
|
|
|
|
|
|
2762
|
|
|
|
|
|
|
#my $Rs = $self->ResultSource->resultset; |
2763
|
0
|
|
|
|
|
0
|
my $Rs = $self->baseResultSet; |
2764
|
|
|
|
|
|
|
|
2765
|
|
|
|
|
|
|
try { |
2766
|
|
|
|
|
|
|
$self->ResultSource->schema->txn_do(sub { |
2767
|
0
|
|
|
|
|
0
|
my @Rows = (); |
2768
|
0
|
|
|
|
|
0
|
foreach my $pk (@$arr) { |
2769
|
0
|
0
|
|
|
|
0
|
my $Row = $Rs->search($self->record_pk_cond($pk))->next or die usererr "Failed to find row by record_pd: $pk"; |
2770
|
|
|
|
|
|
|
|
2771
|
0
|
|
|
|
|
0
|
foreach my $rel (reverse sort @{$self->destroyable_relspec}) { |
|
0
|
|
|
|
|
0
|
|
2772
|
|
|
|
|
|
|
next unless( |
2773
|
0
|
0
|
0
|
|
|
0
|
$rel =~ /^[a-zA-Z0-9\-\_]+$/ |
2774
|
|
|
|
|
|
|
and $Row->can($rel) |
2775
|
|
|
|
|
|
|
); |
2776
|
|
|
|
|
|
|
|
2777
|
0
|
|
|
|
|
0
|
my $relObj = $Row->$rel; |
2778
|
|
|
|
|
|
|
|
2779
|
0
|
0
|
|
|
|
0
|
scream_color(WHITE.ON_RED.BOLD,'DbicLink2 DESTROY --> ' . ref($Row) . '->' . $rel . ' --> ' .$self->get_Row_Rs_label($relObj,1) . "\n") if($self->c->debug); |
2780
|
0
|
0
|
|
|
|
0
|
$relObj->can('delete_all') ? $relObj->delete_all : $relObj->delete; |
2781
|
|
|
|
|
|
|
} |
2782
|
0
|
0
|
|
|
|
0
|
scream_color(WHITE.ON_RED.BOLD,'DbicLink2 DESTROY --> ' . $self->get_Row_Rs_label($Row,1) . "\n") |
2783
|
|
|
|
|
|
|
if($self->c->debug); |
2784
|
0
|
|
|
|
|
0
|
$Row->delete; |
2785
|
|
|
|
|
|
|
} |
2786
|
0
|
|
|
0
|
|
0
|
}); |
2787
|
|
|
|
|
|
|
} |
2788
|
|
|
|
|
|
|
catch { |
2789
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
2790
|
0
|
|
|
|
|
0
|
$self->handle_dbic_exception($err); |
2791
|
|
|
|
|
|
|
#die usererr rawhtml $self->make_dbic_exception_friendly($err), title => 'Database Error'; |
2792
|
0
|
|
|
|
|
0
|
}; |
2793
|
|
|
|
|
|
|
|
2794
|
0
|
|
|
|
|
0
|
return 1; |
2795
|
|
|
|
|
|
|
} |
2796
|
|
|
|
|
|
|
|
2797
|
|
|
|
|
|
|
|
2798
|
|
|
|
|
|
|
|
2799
|
|
|
|
|
|
|
sub extract_db_error_from_exception { |
2800
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2801
|
0
|
|
|
|
|
0
|
my $exception = shift; |
2802
|
0
|
0
|
|
|
|
0
|
die $exception if (ref($exception) =~ /^RapidApp\:\:Responder/); |
2803
|
|
|
|
|
|
|
|
2804
|
0
|
|
|
|
|
0
|
warn $exception; |
2805
|
|
|
|
|
|
|
|
2806
|
0
|
|
|
|
|
0
|
my $msg = "" . $exception . ""; |
2807
|
|
|
|
|
|
|
|
2808
|
0
|
|
|
|
|
0
|
my @parts = split(/DBD\:\:.+\:\:st execute failed\:\s*/,$msg); |
2809
|
0
|
0
|
|
|
|
0
|
return undef unless (scalar @parts > 1); |
2810
|
|
|
|
|
|
|
|
2811
|
0
|
|
|
|
|
0
|
$msg = $parts[1]; |
2812
|
0
|
|
|
|
|
0
|
@parts = split(/\s*\[/,$msg); |
2813
|
|
|
|
|
|
|
|
2814
|
0
|
|
|
|
|
0
|
return $parts[0]; |
2815
|
|
|
|
|
|
|
} |
2816
|
|
|
|
|
|
|
|
2817
|
|
|
|
|
|
|
|
2818
|
|
|
|
|
|
|
sub handle_dbic_exception { |
2819
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2820
|
0
|
|
|
|
|
0
|
my $exception = shift; |
2821
|
|
|
|
|
|
|
|
2822
|
0
|
|
|
|
|
0
|
my $msg = $self->extract_db_error_from_exception($exception); |
2823
|
0
|
0
|
|
|
|
0
|
$msg = $msg ? "$msg\n\n----------------\n" : ''; |
2824
|
|
|
|
|
|
|
|
2825
|
0
|
|
|
|
|
0
|
my $html = '<pre>' . $msg . $exception . "</pre>"; |
2826
|
|
|
|
|
|
|
|
2827
|
0
|
|
|
|
|
0
|
die usererr rawhtml $html, title => "Database Error $append_exception_title"; |
2828
|
|
|
|
|
|
|
|
2829
|
|
|
|
|
|
|
#die $exception if (ref($exception) =~ /^RapidApp\:\:Responder/); |
2830
|
|
|
|
|
|
|
#die usererr rawhtml $self->make_dbic_exception_friendly($exception), title => 'DbicLink2 Database Error'; |
2831
|
|
|
|
|
|
|
} |
2832
|
|
|
|
|
|
|
|
2833
|
|
|
|
|
|
|
|
2834
|
|
|
|
|
|
|
sub make_dbic_exception_friendly { |
2835
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
2836
|
0
|
|
|
|
|
0
|
my $exception = shift; |
2837
|
|
|
|
|
|
|
|
2838
|
0
|
|
|
|
|
0
|
warn $exception; |
2839
|
|
|
|
|
|
|
|
2840
|
0
|
|
|
|
|
0
|
my $msg = "" . $exception . ""; |
2841
|
|
|
|
|
|
|
|
2842
|
|
|
|
|
|
|
|
2843
|
|
|
|
|
|
|
#### Fix me!!!! #### |
2844
|
|
|
|
|
|
|
# Randomly getting this DBIx exception when throwing a customprompt object within CRUD operations |
2845
|
|
|
|
|
|
|
# no idea silently pass it up for now |
2846
|
0
|
0
|
|
|
|
0
|
die infostatus msg => "Bizarre copy of HASH in aassign", status => 500 if ($msg =~/Bizarre copy of HASH in aassign/); |
2847
|
|
|
|
|
|
|
|
2848
|
|
|
|
|
|
|
|
2849
|
|
|
|
|
|
|
|
2850
|
0
|
|
|
|
|
0
|
my @parts = split(/DBD\:\:mysql\:\:st execute failed\:\s*/,$msg); |
2851
|
0
|
0
|
|
|
|
0
|
return $exception unless (scalar @parts > 1); |
2852
|
|
|
|
|
|
|
|
2853
|
0
|
|
|
|
|
0
|
$msg = $parts[1]; |
2854
|
|
|
|
|
|
|
|
2855
|
0
|
|
|
|
|
0
|
@parts = split(/\s*\[/,$msg); |
2856
|
|
|
|
|
|
|
|
2857
|
0
|
|
|
|
|
0
|
return '<center><pre>' . $parts[0] . "</pre></center>"; |
2858
|
0
|
|
|
|
|
0
|
return $parts[0]; |
2859
|
|
|
|
|
|
|
} |
2860
|
|
|
|
|
|
|
|
2861
|
|
|
|
|
|
|
|
2862
|
|
|
|
|
|
|
sub param_decodeIf { |
2863
|
51
|
|
|
51
|
0
|
400
|
my $self = shift; |
2864
|
51
|
|
|
|
|
112
|
my $param = shift; |
2865
|
51
|
|
100
|
|
|
167
|
my $default = shift || undef; |
2866
|
|
|
|
|
|
|
|
2867
|
51
|
100
|
|
|
|
183
|
return $default unless (defined $param); |
2868
|
|
|
|
|
|
|
|
2869
|
14
|
100
|
|
|
|
71
|
return $param if (ref $param); |
2870
|
11
|
|
|
|
|
26
|
my $decoded; |
2871
|
|
|
|
|
|
|
try { |
2872
|
11
|
|
|
11
|
|
968
|
$decoded = $self->json->decode($param); |
2873
|
|
|
|
|
|
|
} |
2874
|
|
|
|
|
|
|
catch { |
2875
|
0
|
|
|
0
|
|
0
|
my $err = shift; |
2876
|
0
|
|
|
|
|
0
|
confess "$err \n\nINPUT STRING: '$param'\n\n"; |
2877
|
11
|
|
|
|
|
148
|
}; |
2878
|
11
|
|
|
|
|
9062
|
return $decoded; |
2879
|
|
|
|
|
|
|
} |
2880
|
|
|
|
|
|
|
|
2881
|
|
|
|
|
|
|
|
2882
|
|
|
|
|
|
|
# This is a DbicLink2-specific implementation of batch_update. Overrides generic method |
2883
|
|
|
|
|
|
|
# in DataStore2. It is able to perform much better with large batches |
2884
|
|
|
|
|
|
|
sub batch_update { |
2885
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
2886
|
|
|
|
|
|
|
|
2887
|
|
|
|
|
|
|
# See DataStore2: |
2888
|
0
|
|
|
|
|
|
$self->before_batch_update; |
2889
|
|
|
|
|
|
|
|
2890
|
0
|
|
|
|
|
|
my $editSpec = $self->param_decodeIf($self->c->req->params->{editSpec}); |
2891
|
0
|
|
|
|
|
|
my $read_params = $editSpec->{read_params}; |
2892
|
0
|
|
|
|
|
|
my $update = $editSpec->{update}; |
2893
|
|
|
|
|
|
|
|
2894
|
0
|
|
|
|
|
|
delete $read_params->{start}; |
2895
|
0
|
|
|
|
|
|
delete $read_params->{limit}; |
2896
|
|
|
|
|
|
|
|
2897
|
0
|
|
|
|
|
|
my %orig_params = %{$self->c->req->params}; |
|
0
|
|
|
|
|
|
|
2898
|
0
|
|
|
|
|
|
%{$self->c->req->params} = %$read_params; |
|
0
|
|
|
|
|
|
|
2899
|
0
|
|
|
|
|
|
my $Rs = $self->get_read_records_Rs($read_params); |
2900
|
0
|
|
|
|
|
|
%{$self->c->req->params} = %orig_params; |
|
0
|
|
|
|
|
|
|
2901
|
|
|
|
|
|
|
|
2902
|
|
|
|
|
|
|
# Remove select/as so the columns are normal (these select/as attrs only apply to read_records) |
2903
|
0
|
|
|
|
|
|
delete $Rs->{attrs}->{select}; |
2904
|
0
|
|
|
|
|
|
delete $Rs->{attrs}->{as}; |
2905
|
|
|
|
|
|
|
|
2906
|
0
|
|
|
|
|
|
my $total = $Rs->pager->total_entries; |
2907
|
|
|
|
|
|
|
|
2908
|
|
|
|
|
|
|
die usererr "Update count mismatch (" . |
2909
|
|
|
|
|
|
|
$editSpec->{count} . ' vs ' . $total . ') ' . |
2910
|
|
|
|
|
|
|
"- This can happen if someone else modified one or more of the records in the update set.\n\n" . |
2911
|
|
|
|
|
|
|
"Reload the the grid and try again." |
2912
|
0
|
0
|
|
|
|
|
unless ($editSpec->{count} == $total); |
2913
|
|
|
|
|
|
|
|
2914
|
0
|
0
|
|
|
|
|
my @columns = grep { $_ ne $self->record_pk && $_ ne 'loadContentCnf' } keys %$update; |
|
0
|
|
|
|
|
|
|
2915
|
0
|
|
|
|
|
|
@columns = $self->TableSpec->filter_updatable_columns(@columns); |
2916
|
|
|
|
|
|
|
|
2917
|
|
|
|
|
|
|
try { |
2918
|
|
|
|
|
|
|
$self->ResultSource->schema->txn_do(sub { |
2919
|
|
|
|
|
|
|
|
2920
|
0
|
|
|
|
|
|
my $ignore_current = 1; |
2921
|
0
|
|
|
|
|
|
my @update_queue = (); |
2922
|
|
|
|
|
|
|
push(@update_queue, $self->prepare_record_updates($_,\@columns,$update,$ignore_current)) |
2923
|
0
|
|
|
|
|
|
for ($Rs->all); |
2924
|
|
|
|
|
|
|
|
2925
|
|
|
|
|
|
|
# Update all the rows at the end: |
2926
|
0
|
|
|
|
|
|
$self->process_update_queue(@update_queue); |
2927
|
0
|
|
|
0
|
|
|
}); |
2928
|
|
|
|
|
|
|
} |
2929
|
|
|
|
|
|
|
catch { |
2930
|
0
|
|
|
0
|
|
|
my $err = shift; |
2931
|
0
|
|
|
|
|
|
$self->handle_dbic_exception($err); |
2932
|
0
|
|
|
|
|
|
}; |
2933
|
|
|
|
|
|
|
|
2934
|
0
|
|
|
|
|
|
return 1; |
2935
|
|
|
|
|
|
|
} |
2936
|
|
|
|
|
|
|
|
2937
|
|
|
|
|
|
|
|
2938
|
|
|
|
|
|
|
1; |