line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package UR::Namespace::Command::Update::ClassesFromDb; |
3
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
27252
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
50
|
|
5
|
2
|
|
|
2
|
|
9
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
40
|
|
6
|
2
|
|
|
2
|
|
8
|
use UR; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
13
|
|
7
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION; |
8
|
2
|
|
|
2
|
|
833
|
use Text::Diff; |
|
2
|
|
|
|
|
11799
|
|
|
2
|
|
|
|
|
3851
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
UR::Object::Type->define( |
11
|
|
|
|
|
|
|
class_name => __PACKAGE__, |
12
|
|
|
|
|
|
|
is => 'UR::Namespace::Command::RunsOnModulesInTree', |
13
|
|
|
|
|
|
|
has => [ |
14
|
|
|
|
|
|
|
data_source => { is => 'List', is_optional => 1, doc => 'Limit updates to these data sources' }, |
15
|
|
|
|
|
|
|
force_check_all_tables => { is => 'Boolean', is_optional => 1, doc => 'By default we only look at tables with a new DDL time for changed database schema information. This explicitly (slowly) checks each table against our cache.' }, |
16
|
|
|
|
|
|
|
force_rewrite_all_classes => { is => 'Boolean', is_optional => 1, doc => 'By default we only rewrite classes where there are database changes. Set this flag to rewrite all classes even where there are no schema changes.' }, |
17
|
|
|
|
|
|
|
table_name => { is => 'List', is_optional => 1, doc => 'Update the specified table.' }, |
18
|
|
|
|
|
|
|
class_name => { is => 'List', is_optional => 1, doc => 'Update only the specified classes.' }, |
19
|
|
|
|
|
|
|
], |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
0
|
|
|
0
|
0
|
|
sub sub_command_sort_position { 2 }; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub help_brief { |
25
|
0
|
|
|
0
|
0
|
|
"Update class definitions (and data dictionary cache) to reflect changes in the database schema." |
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub help_detail { |
29
|
0
|
|
|
0
|
0
|
|
return <
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Reads from the data sources in the current working directory's namespace, |
32
|
|
|
|
|
|
|
and updates the local class tree. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
This hits the data dictionary for the remote database, and gets changes there |
35
|
|
|
|
|
|
|
first. Those changes are then used to mutate the class tree. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
If specific data sources are specified on the command-line, it will limit |
38
|
|
|
|
|
|
|
its database examination to just data in those data sources. This command |
39
|
|
|
|
|
|
|
will, however, always load ALL classes in the namespace when doing this update, |
40
|
|
|
|
|
|
|
to find classes which currently reference the updated table, or are connected |
41
|
|
|
|
|
|
|
to its class indirectly. |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
EOS |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub create { |
49
|
0
|
|
|
0
|
1
|
|
my($class,%params) = @_; |
50
|
|
|
|
|
|
|
|
51
|
0
|
|
|
|
|
|
for my $param_name (qw/data_source class_name table_name/) { |
52
|
0
|
0
|
0
|
|
|
|
if (exists $params{$param_name} && ! ref($params{$param_name})) { |
53
|
|
|
|
|
|
|
# Make sure the data_source parameter is always a listref, even if there's only one item |
54
|
0
|
|
|
|
|
|
$params{$param_name} = [ $params{$param_name} ]; |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# This is used by the test case to turn on no-commit for the metadata DB, |
59
|
|
|
|
|
|
|
# but still have _sync_filesystem write out the modules |
60
|
0
|
|
|
|
|
|
my $override = delete $params{'_override_no_commit_for_filesystem_items'}; |
61
|
|
|
|
|
|
|
|
62
|
0
|
|
|
|
|
|
my $obj = $class->SUPER::create(%params); |
63
|
0
|
0
|
|
|
|
|
return unless $obj; |
64
|
|
|
|
|
|
|
|
65
|
0
|
0
|
|
|
|
|
$obj->{'_override_no_commit_for_filesystem_items'} = $override if $override; |
66
|
|
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
return $obj; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
our @dd_classes = ( |
72
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::Table', |
73
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::TableColumn', |
74
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::FkConstraint', |
75
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::Table::Ghost', |
76
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::TableColumn::Ghost', |
77
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::FkConstraint::Ghost', |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub execute { |
81
|
0
|
|
|
0
|
|
|
my $self = shift; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# |
84
|
|
|
|
|
|
|
# Command parameter checking |
85
|
|
|
|
|
|
|
# |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
|
my $force_check_all_tables = $self->force_check_all_tables; |
88
|
0
|
|
|
|
|
|
my $force_rewrite_all_classes = $self->force_rewrite_all_classes; |
89
|
|
|
|
|
|
|
|
90
|
0
|
|
|
|
|
|
my $namespace = $self->namespace_name; |
91
|
0
|
|
|
|
|
|
$self->status_message("Updating namespace: $namespace\n"); |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
|
my @namespace_data_sources = $namespace->get_data_sources; |
94
|
|
|
|
|
|
|
|
95
|
0
|
|
|
|
|
|
my $specified_table_name_arrayref = $self->table_name; |
96
|
0
|
|
|
|
|
|
my $specified_data_source_arrayref = $self->data_source; |
97
|
0
|
|
|
|
|
|
my $specified_class_name_arrayref = $self->class_name; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
my @data_dictionary_objects; |
101
|
|
|
|
|
|
|
|
102
|
0
|
0
|
0
|
|
|
|
if ($specified_class_name_arrayref or $specified_table_name_arrayref) { |
103
|
0
|
|
|
|
|
|
my $ds_table_list; |
104
|
0
|
0
|
|
|
|
|
if ($specified_class_name_arrayref) { |
105
|
|
|
|
|
|
|
$ds_table_list = [ |
106
|
0
|
|
|
|
|
|
map { [$_->data_source, $_->table_name] } |
107
|
0
|
|
|
|
|
|
grep { $_->data_source } |
108
|
0
|
|
|
|
|
|
map { $_->__meta__ } |
|
0
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
@$specified_class_name_arrayref |
110
|
|
|
|
|
|
|
]; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
else { |
113
|
|
|
|
|
|
|
$ds_table_list = [ |
114
|
0
|
|
|
|
|
|
map { [$_->data_source, $_->table_name] } |
|
0
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
UR::DataSource::RDBMS::Table->get(table_name => $specified_table_name_arrayref) |
116
|
|
|
|
|
|
|
]; |
117
|
0
|
|
|
|
|
|
for my $item (@$ds_table_list) { |
118
|
0
|
|
|
|
|
|
UR::Object::Type->get(data_source => $item->[0], table_name => $item->[1]); |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
for my $item (@$ds_table_list) { |
123
|
0
|
|
|
|
|
|
my ($data_source, $table_name) = @$item; |
124
|
0
|
|
|
|
|
|
$self->_update_database_metadata_objects_for_schema_changes( |
125
|
|
|
|
|
|
|
data_source => $data_source, |
126
|
|
|
|
|
|
|
force_check_all_tables => $force_check_all_tables, |
127
|
|
|
|
|
|
|
table_name => $table_name, |
128
|
|
|
|
|
|
|
); |
129
|
0
|
|
|
|
|
|
for my $dd_class (qw/UR::DataSource::RDBMS::Table UR::DataSource::RDBMS::FkConstraint UR::DataSource::RDBMS::TableColumn/) { |
130
|
0
|
|
|
|
|
|
push @data_dictionary_objects, |
131
|
|
|
|
|
|
|
$dd_class->get(data_source_obj => $data_source, table_name => $table_name); |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
else { |
136
|
|
|
|
|
|
|
# Do the update by data source, all or whatever is specified. |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# |
139
|
|
|
|
|
|
|
# Determine which data sources to update from. |
140
|
|
|
|
|
|
|
# By default, we do all datasources owned by the namespace. |
141
|
|
|
|
|
|
|
# |
142
|
|
|
|
|
|
|
|
143
|
0
|
|
|
|
|
|
my @target_data_sources; |
144
|
0
|
0
|
|
|
|
|
if ($specified_data_source_arrayref) { |
145
|
0
|
|
|
|
|
|
@target_data_sources = (); |
146
|
0
|
|
|
|
|
|
my %data_source_is_specified = map { $_ => 1 } @$specified_data_source_arrayref; |
|
0
|
|
|
|
|
|
|
147
|
0
|
|
|
|
|
|
for my $ds (@namespace_data_sources) { |
148
|
0
|
0
|
|
|
|
|
if ($data_source_is_specified{$ds->id}) { |
149
|
0
|
|
|
|
|
|
push @target_data_sources, $ds; |
150
|
0
|
|
|
|
|
|
delete $data_source_is_specified{$ds->id}; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
#delete @data_source_is_specified{@namespace_data_sources}; |
154
|
0
|
0
|
|
|
|
|
if (my @unknown = keys %data_source_is_specified) { |
155
|
|
|
|
|
|
|
$self->error_message( |
156
|
|
|
|
|
|
|
"Unknown data source(s) for namespace $namespace: @unknown!\n" |
157
|
|
|
|
|
|
|
. "Select from:\n" |
158
|
0
|
|
|
|
|
|
. join("\n",map { $_->id } @namespace_data_sources) |
|
0
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
. "\n" |
160
|
|
|
|
|
|
|
); |
161
|
0
|
|
|
|
|
|
return; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
} else { |
164
|
|
|
|
|
|
|
# Don't update the Meta datasource, unless they specificly asked for it |
165
|
0
|
|
|
|
|
|
@target_data_sources = grep { $_->id !~ /::Meta$/ } @namespace_data_sources; |
|
0
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Some data sources can't handle the magic required for automatic class updating... |
169
|
0
|
|
|
|
|
|
@target_data_sources = grep { $_->can('get_table_names') } @target_data_sources; |
|
0
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
$self->status_message("Found data sources: " |
172
|
|
|
|
|
|
|
. join(", " , |
173
|
0
|
0
|
|
|
|
|
map { /${namespace}::DataSource::(.*)$/; $1 || $_ } |
|
0
|
|
|
|
|
|
|
174
|
0
|
|
|
|
|
|
map { $_->id } |
|
0
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
@target_data_sources |
176
|
|
|
|
|
|
|
) |
177
|
|
|
|
|
|
|
); |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# |
180
|
|
|
|
|
|
|
# A copy of the database metadata is in the ::Meta sqlite datasource. |
181
|
|
|
|
|
|
|
# Get updates to it first. |
182
|
|
|
|
|
|
|
# |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
##$DB::single = 1; |
185
|
|
|
|
|
|
|
|
186
|
0
|
|
|
|
|
|
for my $data_source (@target_data_sources) { |
187
|
|
|
|
|
|
|
# ensure the class has been lazy-loaded until UNIVERSAL::can is smarter... |
188
|
0
|
|
|
|
|
|
$data_source->class; |
189
|
0
|
|
|
|
|
|
$self->status_message("Checking " . $data_source->id . " for schema changes ..."); |
190
|
0
|
|
|
|
|
|
my $success = |
191
|
|
|
|
|
|
|
$self->_update_database_metadata_objects_for_schema_changes( |
192
|
|
|
|
|
|
|
data_source => $data_source, |
193
|
|
|
|
|
|
|
force_check_all_tables => $force_check_all_tables, |
194
|
|
|
|
|
|
|
); |
195
|
0
|
0
|
|
|
|
|
unless ($success) { |
196
|
0
|
|
|
|
|
|
return; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# |
201
|
|
|
|
|
|
|
# Summarize the database changes by table. We'll create/update/delete the class which goes with that table. |
202
|
|
|
|
|
|
|
# |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
##$DB::single = 1; |
205
|
|
|
|
|
|
|
|
206
|
0
|
|
|
|
|
|
my $cx = UR::Context->current; |
207
|
0
|
|
|
|
|
|
for my $dd_class (qw/UR::DataSource::RDBMS::Table UR::DataSource::RDBMS::FkConstraint UR::DataSource::RDBMS::TableColumn/) { |
208
|
|
|
|
|
|
|
push @data_dictionary_objects, |
209
|
0
|
0
|
|
|
|
|
grep { $force_rewrite_all_classes ? 1 : $_->__changes__ or exists($_->{'db_saved_uncommitted'}) } |
|
0
|
0
|
|
|
|
|
|
210
|
|
|
|
|
|
|
$cx->all_objects_loaded($dd_class); |
211
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
my $ghost_class = $dd_class . "::Ghost"; |
213
|
0
|
|
|
|
|
|
push @data_dictionary_objects, $cx->all_objects_loaded($ghost_class); |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# The @data_dictionary_objects array has all dd meta which should be used to rewrite classes. |
218
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
|
my %changed_tables; |
220
|
0
|
|
|
|
|
|
for my $obj ( |
221
|
|
|
|
|
|
|
@data_dictionary_objects |
222
|
|
|
|
|
|
|
) { |
223
|
0
|
|
|
|
|
|
my $table; |
224
|
0
|
0
|
0
|
|
|
|
if ($obj->can("get_table")) { |
|
|
0
|
|
|
|
|
|
225
|
0
|
|
|
|
|
|
$table = $obj->get_table; |
226
|
0
|
0
|
|
|
|
|
unless ($table) { |
227
|
0
|
|
|
|
|
|
Carp::confess("No table object for $obj" . $obj->id); |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
elsif ($obj->isa("UR::DataSource::RDBMS::Table") or $obj->isa("UR::DataSource::RDBMS::Table::Ghost")) { |
231
|
0
|
|
|
|
|
|
$table = $obj |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
# we may find no table if it was dropped, and this is one of its old cols/constraints |
234
|
0
|
0
|
|
|
|
|
next unless $table; |
235
|
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
$changed_tables{$table->id} = 1; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# Some ill-behaved modules might set no_commit to true at compile time. |
241
|
|
|
|
|
|
|
# Reset it back to whatever it is now after going through the namespace's modules |
242
|
|
|
|
|
|
|
# Note that when we have class info in the metadata DB, this probably won't be |
243
|
|
|
|
|
|
|
# necessary anymore since we won't have to actually load up the .pm files to |
244
|
|
|
|
|
|
|
# discover classes in the namespace |
245
|
|
|
|
|
|
|
|
246
|
0
|
|
|
|
|
|
my $remembered_no_commit_setting = UR::DBI->no_commit(); |
247
|
0
|
|
|
|
|
|
my $remembered_dummy_ids_setting = UR::DataSource->use_dummy_autogenerated_ids(); |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# |
251
|
|
|
|
|
|
|
# Update the classes based-on changes to the database schemas |
252
|
|
|
|
|
|
|
# |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
##$DB::single = 1; |
255
|
|
|
|
|
|
|
|
256
|
0
|
0
|
|
|
|
|
if (@data_dictionary_objects) { |
257
|
0
|
0
|
|
|
|
|
$self->status_message("Found " . keys(%changed_tables) . " tables with changes.") unless $force_rewrite_all_classes; |
258
|
0
|
|
|
|
|
|
$self->status_message("Resolving corresponding class changes..."); |
259
|
0
|
|
|
|
|
|
my $success = |
260
|
|
|
|
|
|
|
$self->_update_class_metadata_objects_to_match_database_metadata_changes( |
261
|
|
|
|
|
|
|
data_dictionary_objects => \@data_dictionary_objects |
262
|
|
|
|
|
|
|
); |
263
|
0
|
0
|
|
|
|
|
unless ($success) { |
264
|
0
|
|
|
|
|
|
return; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
else { |
268
|
0
|
|
|
|
|
|
$self->status_message("No data schema changes."); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
0
|
|
|
|
|
|
UR::DBI->no_commit($remembered_no_commit_setting); |
272
|
0
|
|
|
|
|
|
UR::DataSource->use_dummy_autogenerated_ids($remembered_dummy_ids_setting); |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# |
276
|
|
|
|
|
|
|
# The namespace module may have special rules for creating classes from regular (non-schema) data. |
277
|
|
|
|
|
|
|
# At this point we allow the namespace to adjust the class tree as it chooses. |
278
|
|
|
|
|
|
|
# |
279
|
|
|
|
|
|
|
|
280
|
0
|
|
|
|
|
|
$namespace->class; |
281
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
282
|
|
|
|
|
|
|
$namespace->can("_update_classes_from_data_sources") |
283
|
|
|
|
|
|
|
and not $specified_table_name_arrayref |
284
|
|
|
|
|
|
|
and not $specified_class_name_arrayref |
285
|
|
|
|
|
|
|
and not $specified_data_source_arrayref |
286
|
|
|
|
|
|
|
) { |
287
|
0
|
|
|
|
|
|
$self->status_message("Checking for custom changes for the $namespace namespace..."); |
288
|
0
|
|
|
|
|
|
$namespace->_update_classes_from_data_sources(); |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
0
|
|
|
|
|
|
$self->status_message("Saving metadata changes..."); |
292
|
0
|
|
|
|
|
|
my $sync_success = UR::Context->_sync_databases(); |
293
|
0
|
0
|
|
|
|
|
unless ($sync_success) { |
294
|
|
|
|
|
|
|
##$DB::single = 1; |
295
|
0
|
|
|
|
|
|
$self->error_message("Metadata sync_database failed"); |
296
|
0
|
|
|
|
|
|
UR::Context->_rollback_databases(); |
297
|
0
|
|
|
|
|
|
return; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# |
301
|
|
|
|
|
|
|
# Re-write the class headers for changed classes. |
302
|
|
|
|
|
|
|
# Output a summary report of what has been changed. |
303
|
|
|
|
|
|
|
# This block of logic shold be part of saving class data. |
304
|
|
|
|
|
|
|
# Right now, it's done with a _load() override, no data_source, and this block of code. :( |
305
|
|
|
|
|
|
|
# |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
##$DB::single = 1; |
308
|
|
|
|
|
|
|
|
309
|
0
|
|
|
|
|
|
my $cx = UR::Context->current; |
310
|
0
|
|
|
|
|
|
my @changed_class_meta_objects; |
311
|
|
|
|
|
|
|
my %changed_classes; |
312
|
0
|
|
|
|
|
|
my $module_update_success = eval { |
313
|
0
|
|
|
|
|
|
for my $meta_class (qw/ |
314
|
|
|
|
|
|
|
UR::Object::Type |
315
|
|
|
|
|
|
|
UR::Object::Property |
316
|
|
|
|
|
|
|
/) { |
317
|
0
|
|
|
|
|
|
push @changed_class_meta_objects, grep { $_->__changes__ } $cx->all_objects_loaded($meta_class); |
|
0
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
0
|
|
|
|
|
|
my $ghost_class = $meta_class . "::Ghost"; |
320
|
0
|
|
|
|
|
|
push @changed_class_meta_objects, $cx->all_objects_loaded($ghost_class); |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
|
for my $obj ( |
324
|
|
|
|
|
|
|
@changed_class_meta_objects |
325
|
|
|
|
|
|
|
) { |
326
|
0
|
|
|
|
|
|
my $class_name = $obj->class_name; |
327
|
0
|
0
|
|
|
|
|
next unless $class_name; #if $obj is a ghost, class_name might return undef? |
328
|
0
|
|
|
|
|
|
$changed_classes{$class_name} = 1; |
329
|
|
|
|
|
|
|
} |
330
|
0
|
0
|
|
|
|
|
unless (@changed_class_meta_objects) { |
331
|
0
|
|
|
|
|
|
$self->status_message("No class changes."); |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
my $changed_class_count = scalar(keys %changed_classes); |
335
|
0
|
0
|
|
|
|
|
my $subj = $changed_class_count == 1 ? "class" : "classes"; |
336
|
0
|
|
|
|
|
|
$self->status_message("Resolved changes for $changed_class_count $subj"); |
337
|
|
|
|
|
|
|
|
338
|
0
|
|
|
|
|
|
$self->status_message("Updating the filesystem..."); |
339
|
0
|
|
|
|
|
|
my $success = $self->_sync_filesystem( |
340
|
|
|
|
|
|
|
changed_class_names => [sort keys %changed_classes], |
341
|
|
|
|
|
|
|
); |
342
|
0
|
|
|
|
|
|
return $success; |
343
|
|
|
|
|
|
|
}; |
344
|
|
|
|
|
|
|
|
345
|
0
|
0
|
|
|
|
|
if ($@) { |
|
|
0
|
|
|
|
|
|
346
|
0
|
|
|
|
|
|
$self->error_message("Error updating the filesystem: $@"); |
347
|
0
|
|
|
|
|
|
return; |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
elsif (!$module_update_success) { |
350
|
0
|
|
|
|
|
|
$self->status_message("Error updating filesystem!"); |
351
|
0
|
|
|
|
|
|
return; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
0
|
|
|
|
|
|
$self->status_message("Filesystem update complete."); |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# |
358
|
|
|
|
|
|
|
# This commit actually records the data dictionary changes in the ::Meta datasource sqlite database. |
359
|
|
|
|
|
|
|
# |
360
|
|
|
|
|
|
|
|
361
|
0
|
|
|
|
|
|
$self->status_message("Committing changes to data sources..."); |
362
|
|
|
|
|
|
|
|
363
|
0
|
0
|
|
|
|
|
unless (UR::Context->_commit_databases()) { |
364
|
|
|
|
|
|
|
##$DB::single = 1; |
365
|
0
|
|
|
|
|
|
$self->error_message("Metadata commit failed"); |
366
|
0
|
|
|
|
|
|
return; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# |
371
|
|
|
|
|
|
|
# The logic below is only necessary if this process is run as part of some larger process. |
372
|
|
|
|
|
|
|
# Right now that includes the automated test for this module. |
373
|
|
|
|
|
|
|
# After classes have been updated they won't function properly. |
374
|
|
|
|
|
|
|
# Ungenerate and re-generate each of the classes we touched, so that it functions according to its new spec. |
375
|
|
|
|
|
|
|
# |
376
|
|
|
|
|
|
|
|
377
|
0
|
|
|
|
|
|
$self->status_message("Cleaning up."); |
378
|
|
|
|
|
|
|
|
379
|
0
|
|
|
|
|
|
my $success = 1; |
380
|
0
|
|
|
|
|
|
for my $class_name (sort keys %changed_classes) { |
381
|
0
|
|
|
|
|
|
my $class_obj = UR::Object::Type->get($class_name); |
382
|
0
|
0
|
|
|
|
|
next unless $class_obj; |
383
|
0
|
|
|
|
|
|
$class_obj->ungenerate; |
384
|
0
|
0
|
|
|
|
|
Carp::confess("class $class_name didn't ungenerate properly") if $class_obj->generated; |
385
|
0
|
0
|
|
|
|
|
unless (eval { $class_obj->generate } ) { |
|
0
|
|
|
|
|
|
|
386
|
0
|
|
|
|
|
|
$self->warning_message("Class $class_name didn't re-generate properly: $@"); |
387
|
0
|
|
|
|
|
|
$success = 0; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
0
|
0
|
|
|
|
|
unless ($success) { |
392
|
0
|
|
|
|
|
|
$self->status_message("Errors occurred re-generating some classes after update."); |
393
|
0
|
|
|
|
|
|
return; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# |
397
|
|
|
|
|
|
|
# Done |
398
|
|
|
|
|
|
|
# |
399
|
|
|
|
|
|
|
|
400
|
0
|
|
|
|
|
|
$self->status_message("Update complete."); |
401
|
0
|
|
|
|
|
|
return 1; |
402
|
|
|
|
|
|
|
} |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# |
405
|
|
|
|
|
|
|
# The execute() method above is broken into three parts: |
406
|
|
|
|
|
|
|
# ->_update_database_metadata_objects_for_schema_changes() |
407
|
|
|
|
|
|
|
# ->_update_class_metadata_objects_to_match_database_metadata_changes() |
408
|
|
|
|
|
|
|
# ->_sync_filesystem() |
409
|
|
|
|
|
|
|
# |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub _update_database_metadata_objects_for_schema_changes { |
412
|
0
|
|
|
0
|
|
|
my ($self, %params) = @_; |
413
|
0
|
|
|
|
|
|
my $data_source = delete $params{data_source}; |
414
|
0
|
|
|
|
|
|
my $force_check_all_tables = delete $params{force_check_all_tables}; |
415
|
0
|
|
|
|
|
|
my $table_name = delete $params{table_name}; |
416
|
0
|
0
|
|
|
|
|
die "unknown params " . Dumper(\%params) if keys %params; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
#$data_source = $data_source->class; |
419
|
|
|
|
|
|
|
|
420
|
0
|
|
|
|
|
|
my @changed; |
421
|
|
|
|
|
|
|
|
422
|
0
|
|
|
|
|
|
my $last_ddl_time_for_table_name = {}; |
423
|
0
|
0
|
0
|
|
|
|
if ($data_source->can("get_table_last_ddl_times_by_table_name") and !$force_check_all_tables) { |
424
|
|
|
|
|
|
|
# the driver implements a way to get the last DDL time |
425
|
0
|
|
|
|
|
|
$last_ddl_time_for_table_name = $data_source->get_table_last_ddl_times_by_table_name; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# from the cache of known tables |
429
|
0
|
|
|
|
|
|
my @previous_table_names = $data_source->get_table_names; |
430
|
0
|
|
|
|
|
|
my %previous_table_names = map { $_ => 1 } @previous_table_names; |
|
0
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# from the database now |
433
|
0
|
|
|
|
|
|
my @current_table_names = $data_source->_get_table_names_from_data_dictionary(); |
434
|
0
|
|
|
|
|
|
my %current_table_names = map { s/"|'//g; $_ => $_ } @current_table_names; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
|
436
|
0
|
0
|
|
|
|
|
my %all_table_names = $table_name |
437
|
|
|
|
|
|
|
? ( $table_name => 1 ) |
438
|
|
|
|
|
|
|
: ( %current_table_names, %previous_table_names); |
439
|
|
|
|
|
|
|
|
440
|
0
|
|
|
|
|
|
my $new_object_revision = $UR::Context::current->now(); |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
# handle tables which are new/updated by updating the class |
443
|
0
|
|
|
|
|
|
my (@create,@delete,@update); |
444
|
0
|
|
|
|
|
|
my $pattern = '%-42s'; |
445
|
0
|
|
|
|
|
|
my ($dsn) = ($data_source->id =~ /^.*::DataSource::(.*?)$/); |
446
|
0
|
|
|
|
|
|
for my $table_name (keys %all_table_names) { |
447
|
0
|
|
|
|
|
|
my $last_actual_ddl_time = $last_ddl_time_for_table_name->{$table_name}; |
448
|
|
|
|
|
|
|
|
449
|
0
|
|
|
|
|
|
my $table_object; |
450
|
|
|
|
|
|
|
my $last_recorded_ddl_time; |
451
|
0
|
|
|
|
|
|
my $last_object_revision; |
452
|
|
|
|
|
|
|
|
453
|
0
|
|
|
|
|
|
my $db_table_name = $current_table_names{$table_name}; |
454
|
|
|
|
|
|
|
|
455
|
0
|
|
|
|
|
|
eval { |
456
|
|
|
|
|
|
|
#($table_object) = $data_source->get_tables(table_name => $table_name); |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# Using the above doesn't account for a table switching databases, which happens. |
459
|
|
|
|
|
|
|
# Once the data source is _part_ of the id we'll just have a delete/add, but for now it's an update. |
460
|
0
|
|
|
|
|
|
$table_object = UR::DataSource::RDBMS::Table->get(data_source => $data_source->id, |
461
|
|
|
|
|
|
|
table_name => $table_name); |
462
|
|
|
|
|
|
|
}; |
463
|
|
|
|
|
|
|
|
464
|
0
|
0
|
0
|
|
|
|
if ($current_table_names{$table_name} and not $table_object) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
465
|
|
|
|
|
|
|
# new table |
466
|
0
|
|
|
|
|
|
push @create, $table_name; |
467
|
0
|
0
|
|
|
|
|
$self->status_message( |
468
|
|
|
|
|
|
|
sprintf( |
469
|
|
|
|
|
|
|
"A $pattern Schema changes " . ($last_actual_ddl_time ? "on $last_actual_ddl_time" : ""), |
470
|
|
|
|
|
|
|
$dsn . " " . $table_name |
471
|
|
|
|
|
|
|
) |
472
|
|
|
|
|
|
|
); |
473
|
0
|
|
|
|
|
|
my $table_object = $data_source->refresh_database_metadata_for_table_name($db_table_name); |
474
|
0
|
0
|
|
|
|
|
next unless $table_object; |
475
|
|
|
|
|
|
|
|
476
|
0
|
|
|
|
|
|
$table_object->last_ddl_time($last_ddl_time_for_table_name->{$table_name}); |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
elsif ($current_table_names{$table_name} and $table_object) { |
479
|
|
|
|
|
|
|
# retained table |
480
|
|
|
|
|
|
|
# either we know it changed, or we can't know, so update it anyway |
481
|
0
|
0
|
0
|
|
|
|
if (! exists $last_ddl_time_for_table_name->{$table_name} or |
|
|
|
0
|
|
|
|
|
482
|
|
|
|
|
|
|
! defined $table_object->last_ddl_time or |
483
|
|
|
|
|
|
|
$last_ddl_time_for_table_name->{$table_name} gt $table_object->last_ddl_time |
484
|
|
|
|
|
|
|
) { |
485
|
0
|
|
0
|
|
|
|
my $last_update = $table_object->last_ddl_time || $table_object->last_object_revision; |
486
|
0
|
|
0
|
|
|
|
my $this_update = $last_ddl_time_for_table_name->{$table_name} || ""; |
487
|
0
|
|
|
|
|
|
my $table_object = $data_source->refresh_database_metadata_for_table_name($db_table_name); |
488
|
0
|
0
|
|
|
|
|
unless ($table_object) { |
489
|
|
|
|
|
|
|
##$DB::single = 1; |
490
|
0
|
|
|
|
|
|
print; |
491
|
|
|
|
|
|
|
} |
492
|
0
|
|
|
|
|
|
my @changes = |
493
|
|
|
|
|
|
|
# grep { not ($_->properties == 1 and ($_->properties)[0] eq "last_object_revision") } |
494
|
|
|
|
|
|
|
$table_object->__changes__; |
495
|
0
|
0
|
|
|
|
|
if (@changes) { |
496
|
0
|
|
|
|
|
|
$self->status_message( |
497
|
|
|
|
|
|
|
sprintf("U $pattern Last updated on $last_update. Newer schema changes on $this_update." |
498
|
|
|
|
|
|
|
, $dsn . " " . $table_name |
499
|
|
|
|
|
|
|
) |
500
|
|
|
|
|
|
|
); |
501
|
0
|
|
|
|
|
|
push @update, $table_name; |
502
|
|
|
|
|
|
|
} |
503
|
0
|
|
|
|
|
|
$table_object->last_ddl_time($last_ddl_time_for_table_name->{$table_name}); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
elsif ($table_object and not $current_table_names{$table_name}) { |
507
|
|
|
|
|
|
|
# deleted table |
508
|
0
|
|
|
|
|
|
push @delete, $table_name; |
509
|
0
|
|
0
|
|
|
|
$self->status_message( |
510
|
|
|
|
|
|
|
sprintf( |
511
|
|
|
|
|
|
|
"D $pattern Last updated on %s. Table dropped.", |
512
|
|
|
|
|
|
|
$dsn . " " . $table_name, |
513
|
|
|
|
|
|
|
$last_object_revision || "" |
514
|
|
|
|
|
|
|
) |
515
|
|
|
|
|
|
|
); |
516
|
0
|
|
|
|
|
|
my $table_object = UR::DataSource::RDBMS::Table->get( |
517
|
|
|
|
|
|
|
data_source => $data_source->id, |
518
|
|
|
|
|
|
|
table_name => $table_name, |
519
|
|
|
|
|
|
|
); |
520
|
0
|
|
|
|
|
|
$table_object->delete; |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
else { |
523
|
0
|
|
|
|
|
|
Carp::confess("Unable to categorize table $table_name as new/old/deleted?!"); |
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
0
|
|
|
|
|
|
return 1; |
528
|
|
|
|
|
|
|
} |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
# Keep a cache of class meta objects so we don't have to keep asking the |
533
|
|
|
|
|
|
|
# object system to do it for us. This should be a speed optimization because |
534
|
|
|
|
|
|
|
# the asking eventually filters down to calling get_material_classes() on the |
535
|
|
|
|
|
|
|
# namespace which can be extremely slow. If it's not in the cache, defer to |
536
|
|
|
|
|
|
|
# asking the data source |
537
|
|
|
|
|
|
|
sub _get_class_meta_for_table_name { |
538
|
0
|
|
|
0
|
|
|
my($self,%param) = @_; |
539
|
|
|
|
|
|
|
|
540
|
0
|
|
|
|
|
|
my $data_source = $param{'data_source'}; |
541
|
0
|
|
|
|
|
|
my $data_source_name = $data_source->get_name(); |
542
|
0
|
|
|
|
|
|
my $table_name = $param{'table_name'}; |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
my ($obj) = |
545
|
0
|
|
|
|
|
|
grep { not $_->isa("UR::Object::Ghost") } |
|
0
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
UR::Object::Type->is_loaded( |
547
|
|
|
|
|
|
|
data_source_id => $data_source, |
548
|
|
|
|
|
|
|
table_name => $table_name |
549
|
|
|
|
|
|
|
); |
550
|
0
|
0
|
|
|
|
|
return $obj if $obj; |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
|
553
|
0
|
0
|
|
|
|
|
unless ($self->{'_class_meta_cache'}{$data_source_name}) { |
554
|
|
|
|
|
|
|
my @classes = |
555
|
0
|
|
|
|
|
|
grep { not $_->class_name->isa('UR::Object::Ghost') } |
|
0
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
UR::Object::Type->get(data_source_id => $data_source); |
557
|
|
|
|
|
|
|
|
558
|
0
|
|
|
|
|
|
for my $class (@classes) { |
559
|
0
|
|
|
|
|
|
my $table_name = $class->table_name; |
560
|
0
|
0
|
|
|
|
|
next unless $table_name; |
561
|
0
|
|
|
|
|
|
$self->{'_class_meta_cache'}->{$data_source_name}->{$table_name} = $class; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
0
|
|
|
|
|
|
$obj = $self->{'_class_meta_cache'}->{$data_source_name}->{$table_name}; |
566
|
0
|
0
|
|
|
|
|
return $obj if $obj; |
567
|
0
|
|
|
|
|
|
return; |
568
|
|
|
|
|
|
|
} |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
sub _update_class_metadata_objects_to_match_database_metadata_changes { |
572
|
0
|
|
|
0
|
|
|
my ($self, %params) = @_; |
573
|
|
|
|
|
|
|
|
574
|
0
|
|
|
|
|
|
my $data_dictionary_objects = delete $params{data_dictionary_objects}; |
575
|
0
|
0
|
|
|
|
|
if (%params) { |
576
|
0
|
|
|
|
|
|
$self->error_message("Unknown params!"); |
577
|
0
|
|
|
|
|
|
return; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
# |
581
|
|
|
|
|
|
|
# INITIALIZATION AND SANITY CHECKING |
582
|
|
|
|
|
|
|
# |
583
|
|
|
|
|
|
|
|
584
|
0
|
|
|
|
|
|
my $namespace = $self->namespace_name; |
585
|
|
|
|
|
|
|
|
586
|
0
|
|
|
|
|
|
$self->status_message("Updating classes..."); |
587
|
|
|
|
|
|
|
|
588
|
0
|
|
|
|
|
|
my %dd_changes_by_class = ( |
589
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::Table' => [], |
590
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::TableColumn' => [], |
591
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::FkConstraint' => [], |
592
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::Table::Ghost' => [], |
593
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::TableColumn::Ghost' => [], |
594
|
|
|
|
|
|
|
'UR::DataSource::RDBMS::FkConstraint::Ghost' => [], |
595
|
|
|
|
|
|
|
); |
596
|
0
|
|
|
|
|
|
for my $changed_obj (@$data_dictionary_objects) { |
597
|
0
|
|
|
|
|
|
my $changed_class = $changed_obj->class; |
598
|
0
|
|
|
|
|
|
my $bucket = $dd_changes_by_class{$changed_class}; |
599
|
0
|
|
|
|
|
|
push @$bucket, $changed_obj; |
600
|
|
|
|
|
|
|
} |
601
|
2
|
|
|
2
|
|
14
|
my $sorter = sub($$) { no warnings 'uninitialized'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
1239
|
|
602
|
0
|
0
|
|
0
|
|
|
$_[0]->table_name cmp $_[1]->table_name || $_[0]->id cmp $_[1]->id |
603
|
0
|
|
|
|
|
|
}; |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
# FKs are special, in that they might change names, but we use the name as the "id". |
606
|
|
|
|
|
|
|
# This should change, really, but until it does we need to identify them by their "content", |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# |
609
|
|
|
|
|
|
|
# DELETIONS |
610
|
|
|
|
|
|
|
# |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# DELETED FK CONSTRAINTS |
613
|
|
|
|
|
|
|
# Just detach the object reference meta-data from the constraint. |
614
|
|
|
|
|
|
|
# We only actually delete references when their properties all go away, |
615
|
|
|
|
|
|
|
# which can happen when the columns go away (through table deletion or alteration). |
616
|
|
|
|
|
|
|
# It can also happen when one of the involved classes is deleted, which never happens |
617
|
|
|
|
|
|
|
# automatically. |
618
|
|
|
|
|
|
|
|
619
|
0
|
|
|
|
|
|
for my $fk (sort $sorter @{ $dd_changes_by_class{'UR::DataSource::RDBMS::FkConstraint::Ghost'} }) { |
|
0
|
|
|
|
|
|
|
620
|
0
|
0
|
|
|
|
|
unless ($fk->table_name) { |
621
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No table name for deleted foreign key constraint %-32s\n", $fk->id)); |
622
|
0
|
|
|
|
|
|
next; |
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
|
625
|
0
|
|
|
|
|
|
my $table = $fk->get_table; |
626
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $table->data_source, |
627
|
|
|
|
|
|
|
table_name => $table->table_name); |
628
|
|
|
|
|
|
|
|
629
|
0
|
0
|
|
|
|
|
unless ($class) { |
630
|
|
|
|
|
|
|
##$DB::single = 1; |
631
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No class found for deleted foreign key constraint %-32s %-32s\n",$table->table_name, $fk->id)); |
632
|
0
|
|
|
|
|
|
next; |
633
|
|
|
|
|
|
|
} |
634
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
635
|
0
|
|
|
|
|
|
my $property = UR::Object::Property->get(class_name => $class_name, constraint_name => $fk->fk_constraint_name); |
636
|
0
|
0
|
|
|
|
|
unless ($property) { |
637
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No property found for deleted foreign key constraint %-32s %-32s class $class_name\n", |
638
|
|
|
|
|
|
|
$table->table_name, $fk->fk_constraint_name)); |
639
|
0
|
|
|
|
|
|
next; |
640
|
|
|
|
|
|
|
} |
641
|
0
|
|
|
|
|
|
$property->delete; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
} |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
# DELETED UNIQUE CONSTRAINTS |
646
|
|
|
|
|
|
|
# DELETED PK CONSTRAINTS |
647
|
|
|
|
|
|
|
# We do nothing here, because we don't track these as individual DD objects, just values on the table object. |
648
|
|
|
|
|
|
|
# If a table changes constraints, that is handled below after table/column add/update. |
649
|
|
|
|
|
|
|
# If a table is dropped entirely, we leave all pk/unique constraints in place, |
650
|
|
|
|
|
|
|
# since, if the class is not manually deleted by the developer, it should continue |
651
|
|
|
|
|
|
|
# to function as it did before. |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# DELETED COLUMNS |
654
|
0
|
|
|
|
|
|
my @saved_removed_column_messages; # Delete them now, but report about them later in the 'Updating class properties' section |
655
|
0
|
|
|
|
|
|
for my $column (sort $sorter @{ $dd_changes_by_class{"UR::DataSource::RDBMS::TableColumn::Ghost"} }) { |
|
0
|
|
|
|
|
|
|
656
|
0
|
|
|
|
|
|
my $table = $column->get_table; |
657
|
0
|
0
|
|
|
|
|
unless ($table) { |
658
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No table found for deleted column %-32s\n", $column->id)); |
659
|
0
|
|
|
|
|
|
next; |
660
|
|
|
|
|
|
|
} |
661
|
0
|
|
|
|
|
|
my $column_name = $column->column_name; |
662
|
|
|
|
|
|
|
|
663
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $table->data_source, |
664
|
|
|
|
|
|
|
table_name => $table->table_name); |
665
|
0
|
0
|
|
|
|
|
unless ($class) { |
666
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No class found for deleted column %-32s %-32s\n", $table->table_name, $column_name)); |
667
|
0
|
|
|
|
|
|
next; |
668
|
|
|
|
|
|
|
} |
669
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
670
|
|
|
|
|
|
|
|
671
|
0
|
|
|
|
|
|
my ($property) = $class->direct_property_metas( |
672
|
|
|
|
|
|
|
column_name => $column_name |
673
|
|
|
|
|
|
|
); |
674
|
0
|
0
|
|
|
|
|
unless ($property) { |
675
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No property found for deleted column %-32s %-32s\n",$table->table_name, $column_name)); |
676
|
0
|
|
|
|
|
|
next; |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
0
|
0
|
|
|
|
|
unless ($table->isa("UR::DataSource::RDBMS::Table::Ghost")) { |
680
|
0
|
|
|
|
|
|
push(@saved_removed_column_messages, |
681
|
|
|
|
|
|
|
sprintf("D %-40s property %-16s for removed column %s.%s\n", |
682
|
|
|
|
|
|
|
$class->class_name, |
683
|
|
|
|
|
|
|
$property->property_name, |
684
|
|
|
|
|
|
|
$column->table_name, |
685
|
|
|
|
|
|
|
$column->column_name, |
686
|
|
|
|
|
|
|
) |
687
|
|
|
|
|
|
|
); |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
0
|
|
|
|
|
|
$property->delete; |
691
|
|
|
|
|
|
|
|
692
|
0
|
0
|
|
|
|
|
unless ($property->isa("UR::DeletedRef")) { |
693
|
0
|
|
|
|
|
|
Carp::confess("Error deleting property " . $property->id); |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
# DELETED TABLES |
698
|
0
|
|
|
|
|
|
my %classes_with_deleted_tables; |
699
|
0
|
|
|
|
|
|
for my $table (sort $sorter @{ $dd_changes_by_class{"UR::DataSource::RDBMS::Table::Ghost"} }) { |
|
0
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
# Though we create classes for tables, we don't immediately delete them, just deflate them. |
701
|
0
|
|
|
|
|
|
my $table_name = $table->table_name; |
702
|
0
|
0
|
|
|
|
|
unless ($table_name) { |
703
|
0
|
|
|
|
|
|
$self->status_message("~ No table_name for deleted table object ".$table->id); |
704
|
0
|
|
|
|
|
|
next; |
705
|
|
|
|
|
|
|
} |
706
|
|
|
|
|
|
|
|
707
|
0
|
0
|
|
|
|
|
if (not defined UR::Context->_get_committed_property_value($table,'table_name')) { |
708
|
0
|
|
|
|
|
|
print Data::Dumper::Dumper($table); |
709
|
|
|
|
|
|
|
##$DB::single = 1; |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
# FIXME should this use $data_source->get_class_meta_for_table($table) instead? |
712
|
0
|
|
|
|
|
|
my $committed_data_source_id = UR::Context->_get_committed_property_value($table,'data_source'); |
713
|
0
|
|
|
|
|
|
my $committed_table_name = UR::Context->_get_committed_property_value($table,'table_name'); |
714
|
0
|
|
|
|
|
|
my $class = UR::Object::Type->get( |
715
|
|
|
|
|
|
|
data_source_id => $committed_data_source_id, |
716
|
|
|
|
|
|
|
table_name => $committed_table_name, |
717
|
|
|
|
|
|
|
); |
718
|
0
|
0
|
|
|
|
|
unless ($class) { |
719
|
0
|
|
|
|
|
|
$self->status_message(sprintf("~ No class found for deleted table %-32s" . "\n",$table->id)); |
720
|
0
|
|
|
|
|
|
next; |
721
|
|
|
|
|
|
|
} |
722
|
0
|
|
|
|
|
|
$classes_with_deleted_tables{$table_name} = $class; |
723
|
0
|
|
|
|
|
|
$class->data_source(undef); |
724
|
0
|
|
|
|
|
|
$class->table_name(undef); |
725
|
|
|
|
|
|
|
} # next deleted table |
726
|
|
|
|
|
|
|
|
727
|
0
|
|
|
|
|
|
for my $table_name (keys %classes_with_deleted_tables) { |
728
|
0
|
|
|
|
|
|
my $class = $classes_with_deleted_tables{$table_name}; |
729
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
730
|
|
|
|
|
|
|
|
731
|
0
|
|
|
|
|
|
my %ancestory = map { $_ => 1 } $class->inheritance; |
|
0
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
my @ancestors_with_tables = |
733
|
|
|
|
|
|
|
grep { |
734
|
0
|
|
0
|
|
|
|
$a = UR::Object::Type->get(class_name => $_) |
|
0
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
|| UR::Object::Type::Ghost->get(class_name => $_); |
736
|
0
|
0
|
|
|
|
|
$a && $a->table_name; |
737
|
|
|
|
|
|
|
} sort keys %ancestory; |
738
|
0
|
0
|
|
|
|
|
if (@ancestors_with_tables) { |
739
|
0
|
|
|
|
|
|
$self->status_message( |
740
|
|
|
|
|
|
|
sprintf("U %-40s class is now detached from deleted table %-32s. It still inherits from classes with persistent storage." . "\n",$class_name,$table_name) |
741
|
|
|
|
|
|
|
); |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
else { |
744
|
0
|
|
|
|
|
|
$self->status_message( |
745
|
|
|
|
|
|
|
sprintf("D %-40s class deleted for deleted table %s" . "\n",$class_name,$table_name) |
746
|
|
|
|
|
|
|
); |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
} # next deleted table |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
# This is the data structure used by _get_class_meta_for_table_name |
751
|
|
|
|
|
|
|
# There's a bad interaction with software transactions that can lead |
752
|
|
|
|
|
|
|
# to this cache containing deleted class objects if the caller holds |
753
|
|
|
|
|
|
|
# on to a reference to this command object and repetedly calls execute() |
754
|
|
|
|
|
|
|
# but rolls back transactions between those calls. |
755
|
0
|
|
|
|
|
|
$self->{'_class_meta_cache'} = {}; |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
##$DB::single = 1; |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
# |
760
|
|
|
|
|
|
|
# EXISTING DD OBJECTS |
761
|
|
|
|
|
|
|
# |
762
|
|
|
|
|
|
|
# TABLE |
763
|
0
|
|
|
|
|
|
for my $table (sort $sorter @{ $dd_changes_by_class{"UR::DataSource::RDBMS::Table"} }) { |
|
0
|
|
|
|
|
|
|
764
|
0
|
|
|
|
|
|
my $table_name = $table->table_name; |
765
|
0
|
|
|
|
|
|
my $data_source = $table->data_source; |
766
|
|
|
|
|
|
|
|
767
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $data_source, |
768
|
|
|
|
|
|
|
table_name => $table_name); |
769
|
|
|
|
|
|
|
|
770
|
0
|
0
|
|
|
|
|
if ($class) { |
771
|
|
|
|
|
|
|
# update |
772
|
|
|
|
|
|
|
|
773
|
0
|
0
|
|
|
|
|
if ($class->data_source ne $table->data_source) { |
774
|
0
|
|
|
|
|
|
$class->data_source($table->data_source); |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
|
777
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
778
|
2
|
|
|
2
|
|
13
|
no warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1102
|
|
779
|
0
|
0
|
|
|
|
|
if ($table->remarks ne UR::Context->_get_committed_property_value($table,'remarks')) { |
780
|
0
|
|
|
|
|
|
$class->doc($table->remarks); |
781
|
|
|
|
|
|
|
} |
782
|
0
|
0
|
|
|
|
|
if ($table->data_source ne UR::Context->_get_committed_property_value($table,'data_source')) { |
783
|
0
|
|
|
|
|
|
$class->data_source($table->data_source); |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
|
786
|
0
|
0
|
|
|
|
|
if ($class->__changes__) { |
787
|
0
|
|
|
|
|
|
$self->status_message( |
788
|
|
|
|
|
|
|
sprintf("U %-40s class uses %s %s %s" . "\n", |
789
|
|
|
|
|
|
|
$class_name, |
790
|
|
|
|
|
|
|
$table->data_source->get_name, |
791
|
|
|
|
|
|
|
lc($table->table_type), |
792
|
|
|
|
|
|
|
$table_name) |
793
|
|
|
|
|
|
|
); |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
} |
796
|
|
|
|
|
|
|
else { |
797
|
|
|
|
|
|
|
# create |
798
|
0
|
|
|
|
|
|
my $data_source = $table->data_source; |
799
|
0
|
0
|
|
|
|
|
my $data_source_id = (ref $data_source ? $data_source->id : $data_source); |
800
|
0
|
|
|
|
|
|
my $class_name = $data_source->resolve_class_name_for_table_name($table_name,$table->table_type); |
801
|
0
|
0
|
|
|
|
|
unless ($class_name) { |
802
|
0
|
|
|
|
|
|
Carp::confess( |
803
|
|
|
|
|
|
|
"Failed to resolve a class name for new table " |
804
|
|
|
|
|
|
|
. $table_name |
805
|
|
|
|
|
|
|
); |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
# if the original table_name was empty (ie. not backed by a table), and the |
809
|
|
|
|
|
|
|
# new one actually has a table, then this is just another schema change and |
810
|
|
|
|
|
|
|
# not an error. Set the table_name attribute and go on... |
811
|
0
|
|
|
|
|
|
my $class = UR::Object::Type->get(class_name => $class_name); |
812
|
0
|
0
|
|
|
|
|
my $prev_table_name = ($class ? $class->table_name : undef); |
813
|
0
|
0
|
|
|
|
|
my $prev_data_source_id = ($class ? $class->data_source_id : undef); |
814
|
0
|
0
|
0
|
|
|
|
if ($class && $prev_table_name) { |
815
|
|
|
|
|
|
|
|
816
|
0
|
|
|
|
|
|
Carp::confess( |
817
|
|
|
|
|
|
|
"Class $class_name already exists for table '$prev_table_name' in $prev_data_source_id." |
818
|
|
|
|
|
|
|
. " Cannot generate class for $table_name in $data_source_id." |
819
|
|
|
|
|
|
|
); |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
$self->status_message( |
823
|
0
|
|
|
|
|
|
sprintf("A %-40s class uses %s %s %s" . "\n", |
824
|
|
|
|
|
|
|
$class_name, |
825
|
|
|
|
|
|
|
$table->data_source->get_name, |
826
|
|
|
|
|
|
|
lc($table->table_type), |
827
|
|
|
|
|
|
|
$table_name) |
828
|
|
|
|
|
|
|
); |
829
|
|
|
|
|
|
|
|
830
|
0
|
0
|
|
|
|
|
if ($class) { |
831
|
0
|
0
|
|
|
|
|
$class->doc($table->remarks ? $table->remarks: undef); |
832
|
0
|
|
|
|
|
|
$class->data_source($data_source); |
833
|
0
|
|
|
|
|
|
$class->table_name($table_name); |
834
|
0
|
|
|
|
|
|
$class->er_role($table->er_type); |
835
|
|
|
|
|
|
|
} else { |
836
|
0
|
0
|
|
|
|
|
$class = UR::Object::Type->create( |
837
|
|
|
|
|
|
|
class_name => $class_name, |
838
|
|
|
|
|
|
|
doc => ($table->remarks ? $table->remarks: undef), |
839
|
|
|
|
|
|
|
data_source_id => $data_source, |
840
|
|
|
|
|
|
|
table_name => $table_name, |
841
|
|
|
|
|
|
|
er_role => $table->er_type, |
842
|
|
|
|
|
|
|
# generate => 0, |
843
|
|
|
|
|
|
|
); |
844
|
0
|
0
|
|
|
|
|
unless ($class) { |
845
|
0
|
|
|
|
|
|
Carp::confess( |
846
|
|
|
|
|
|
|
"Failed to create class $class_name for new table " |
847
|
|
|
|
|
|
|
. $table_name |
848
|
|
|
|
|
|
|
. ". " . UR::Object::Type->error_message |
849
|
|
|
|
|
|
|
); |
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
} |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
} # next table |
854
|
|
|
|
|
|
|
|
855
|
0
|
|
|
|
|
|
$self->status_message("Updating direct class properties...\n"); |
856
|
|
|
|
|
|
|
|
857
|
0
|
|
|
|
|
|
$self->status_message($_) foreach @saved_removed_column_messages; |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
# COLUMN |
860
|
|
|
|
|
|
|
|
861
|
0
|
|
|
|
|
|
for my $column (sort $sorter @{ $dd_changes_by_class{'UR::DataSource::RDBMS::TableColumn'} }) { |
|
0
|
|
|
|
|
|
|
862
|
0
|
|
|
|
|
|
my $table = $column->get_table; |
863
|
0
|
|
|
|
|
|
my $column_name = $column->column_name; |
864
|
0
|
|
|
|
|
|
my $data_source = $table->data_source; |
865
|
0
|
|
|
|
|
|
my($ur_data_type, $default_length) = @{ $data_source->ur_data_type_for_data_source_data_type($column->data_type) }; |
|
0
|
|
|
|
|
|
|
866
|
0
|
0
|
|
|
|
|
my $ur_data_length = defined($column->data_length) ? $column->data_length : $default_length; |
867
|
|
|
|
|
|
|
|
868
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $data_source, |
869
|
|
|
|
|
|
|
table_name => $table->table_name); |
870
|
|
|
|
|
|
|
|
871
|
0
|
0
|
|
|
|
|
unless ($class) { |
872
|
0
|
|
|
|
|
|
$class = $self->_get_class_meta_for_table_name(data_source => $data_source, |
873
|
|
|
|
|
|
|
table_name => $table->table_name); |
874
|
0
|
0
|
|
|
|
|
Carp::confess("Class object missing for table " . $table->table_name) unless $class; |
875
|
|
|
|
|
|
|
} |
876
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
877
|
0
|
|
|
|
|
|
my $property; |
878
|
0
|
|
|
|
|
|
foreach my $prop_object ( $class->direct_property_metas ) { |
879
|
0
|
0
|
0
|
|
|
|
if (defined $prop_object->column_name and lc($prop_object->column_name) eq lc($column_name)) { |
880
|
0
|
|
|
|
|
|
$property = $prop_object; |
881
|
0
|
|
|
|
|
|
last; |
882
|
|
|
|
|
|
|
} |
883
|
|
|
|
|
|
|
} |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
# We care less whether the column is new/updated, than whether there is property metadata for it. |
886
|
0
|
0
|
|
|
|
|
if ($property) { |
887
|
|
|
|
|
|
|
my @column_property_translations = ( |
888
|
|
|
|
|
|
|
# [ column_name, property_name, conversion_sub(column_obj, value) ] |
889
|
|
|
|
|
|
|
['data_length' => 'data_length', |
890
|
|
|
|
|
|
|
# lengths for these data types are based on the number of bytes used internally in the |
891
|
|
|
|
|
|
|
# database. The UR-based objects will store the text version, which will always be longer, |
892
|
|
|
|
|
|
|
# making $obj->__errors__() complain about the length being out of bounds |
893
|
0
|
0
|
|
0
|
|
|
sub { my ($c, $av) = @_; defined($av) ? $av : ($c->is_time_data ? undef : $ur_data_length) } ], |
|
0
|
0
|
|
|
|
|
|
894
|
|
|
|
|
|
|
['data_type' => 'data_type', |
895
|
0
|
0
|
|
0
|
|
|
sub { my ($c, $av) = @_; defined($ur_data_type) ? $ur_data_type : $av } ], |
|
0
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
['nullable' => 'is_optional', |
897
|
0
|
0
|
0
|
0
|
|
|
sub { my ($c, $av) = @_; (defined($av) and ($av eq "Y")) ? 1 : 0 } ], |
|
0
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
['remarks' => 'doc', |
899
|
|
|
|
|
|
|
# Ideally this would only use DB value ($av) if the last_ddl_time was newer. |
900
|
0
|
0
|
|
0
|
|
|
sub { my ($c, $av) = @_; defined($av) ? $av : $property->doc } ], |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
); |
902
|
|
|
|
|
|
|
# update |
903
|
0
|
|
|
|
|
|
for my $translation (@column_property_translations) { |
904
|
0
|
|
|
|
|
|
my ($column_attr, $property_attr, $conversion_sub) = @$translation; |
905
|
0
|
|
0
|
|
|
|
$property_attr ||= $column_attr; |
906
|
|
|
|
|
|
|
|
907
|
2
|
|
|
2
|
|
12
|
no warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
142
|
|
908
|
0
|
0
|
|
|
|
|
if (UR::Context->_get_committed_property_value($column,$column_attr) ne $column->$column_attr) { |
909
|
0
|
0
|
|
|
|
|
if ($conversion_sub) { |
910
|
0
|
|
|
|
|
|
$property->$property_attr($conversion_sub->($column, $column->$column_attr)); |
911
|
|
|
|
|
|
|
} |
912
|
|
|
|
|
|
|
else { |
913
|
0
|
|
|
|
|
|
$property->$property_attr($column->$column_attr); |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
0
|
0
|
|
|
|
|
if ($property->__changes__) { |
919
|
2
|
|
|
2
|
|
8
|
no warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
278
|
|
920
|
0
|
|
|
|
|
|
$self->status_message( |
921
|
|
|
|
|
|
|
sprintf("U %-40s property %-20s for column %s.%s (%s %s)\n", |
922
|
|
|
|
|
|
|
$class_name, |
923
|
|
|
|
|
|
|
$property->property_name, |
924
|
|
|
|
|
|
|
$table->table_name, |
925
|
|
|
|
|
|
|
$column_name, |
926
|
|
|
|
|
|
|
$column->data_type, |
927
|
|
|
|
|
|
|
$column->data_length) |
928
|
|
|
|
|
|
|
); |
929
|
|
|
|
|
|
|
} |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
else { |
932
|
|
|
|
|
|
|
# create |
933
|
0
|
|
|
|
|
|
my $property_name = $data_source->resolve_property_name_for_column_name($column->column_name); |
934
|
0
|
0
|
|
|
|
|
unless ($property_name) { |
935
|
0
|
|
|
|
|
|
Carp::confess( |
936
|
|
|
|
|
|
|
"Failed to resolve a property name for new column " |
937
|
|
|
|
|
|
|
. $column->column_name |
938
|
|
|
|
|
|
|
); |
939
|
|
|
|
|
|
|
} |
940
|
|
|
|
|
|
|
|
941
|
0
|
|
|
|
|
|
my $create_exception; |
942
|
0
|
|
|
|
|
|
for (my $attempt = 0; $attempt < 3; $attempt++) { |
943
|
0
|
0
|
|
|
|
|
$property_name = '_' . $property_name if $attempt; |
944
|
|
|
|
|
|
|
|
945
|
0
|
|
|
|
|
|
$create_exception = do { |
946
|
0
|
|
|
|
|
|
local $@; |
947
|
0
|
|
|
|
|
|
eval { |
948
|
0
|
0
|
|
|
|
|
$property = UR::Object::Property->create( |
949
|
|
|
|
|
|
|
class_name => $class_name, |
950
|
|
|
|
|
|
|
property_name => $property_name, |
951
|
|
|
|
|
|
|
column_name => $column_name, |
952
|
|
|
|
|
|
|
data_type => $ur_data_type, |
953
|
|
|
|
|
|
|
data_length => $ur_data_length, |
954
|
|
|
|
|
|
|
is_optional => $column->nullable eq "Y" ? 1 : 0, |
955
|
|
|
|
|
|
|
is_volatile => 0, |
956
|
|
|
|
|
|
|
doc => $column->remarks, |
957
|
|
|
|
|
|
|
is_specified_in_module_header => 1, |
958
|
|
|
|
|
|
|
); |
959
|
|
|
|
|
|
|
}; |
960
|
0
|
|
|
|
|
|
$@; |
961
|
|
|
|
|
|
|
}; |
962
|
0
|
0
|
|
|
|
|
last if $property; |
963
|
|
|
|
|
|
|
} |
964
|
|
|
|
|
|
|
|
965
|
2
|
|
|
2
|
|
14
|
no warnings 'uninitialized'; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
2512
|
|
966
|
0
|
|
|
|
|
|
$self->status_message( |
967
|
|
|
|
|
|
|
sprintf("A %-40s property %-16s for column %s.%s (%s %s)\n", |
968
|
|
|
|
|
|
|
$class_name, |
969
|
|
|
|
|
|
|
$property->property_name, |
970
|
|
|
|
|
|
|
$table->table_name, |
971
|
|
|
|
|
|
|
$column_name, |
972
|
|
|
|
|
|
|
$column->data_type, |
973
|
|
|
|
|
|
|
$column->data_length) |
974
|
|
|
|
|
|
|
); |
975
|
|
|
|
|
|
|
|
976
|
0
|
0
|
|
|
|
|
unless ($property) { |
977
|
0
|
0
|
|
|
|
|
if ($create_exception =~ m/An object of class UR::Object::Property already exists/) { |
978
|
0
|
|
|
|
|
|
$self->warning_message("Conflicting property names already exist in class $class_name for column $column_name in table ".$table->table_name); |
979
|
|
|
|
|
|
|
} else { |
980
|
0
|
|
|
|
|
|
Carp::confess( |
981
|
|
|
|
|
|
|
"Failed to create property $property_name on class $class_name. " |
982
|
|
|
|
|
|
|
. UR::Object::Property->error_message |
983
|
|
|
|
|
|
|
); |
984
|
|
|
|
|
|
|
} |
985
|
|
|
|
|
|
|
} |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
} # next column |
988
|
|
|
|
|
|
|
|
989
|
0
|
|
|
|
|
|
$self->status_message("Updating class ID properties...\n"); |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
# PK CONSTRAINTS (loop table objects again, since the DD doesn't do individual ID objects) |
992
|
0
|
|
|
|
|
|
for my $table (sort $sorter @{ $dd_changes_by_class{'UR::DataSource::RDBMS::Table'} }) { |
|
0
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
# created/updated/unchanged |
994
|
|
|
|
|
|
|
# delete and re-create these objects: they're "bridges", so no developer supplied data is presesent |
995
|
0
|
|
|
|
|
|
my $table_name = $table->table_name; |
996
|
|
|
|
|
|
|
|
997
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $table->data_source, |
998
|
|
|
|
|
|
|
table_name => $table_name); |
999
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
1000
|
0
|
|
|
|
|
|
my @properties = UR::Object::Property->get(class_name => $class_name); |
1001
|
|
|
|
|
|
|
|
1002
|
0
|
0
|
|
|
|
|
unless (@properties) { |
1003
|
0
|
|
|
|
|
|
$self->warning_message("no properties on class $class_name?"); |
1004
|
|
|
|
|
|
|
##$DB::single = 1; |
1005
|
|
|
|
|
|
|
} |
1006
|
|
|
|
|
|
|
|
1007
|
0
|
|
|
|
|
|
my @expected_pk_cols = grep { defined } |
1008
|
0
|
|
|
|
|
|
map { $_->column_name } |
1009
|
0
|
|
|
|
|
|
grep { defined $_->is_id } |
|
0
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
@properties; |
1011
|
|
|
|
|
|
|
|
1012
|
0
|
|
|
|
|
|
my @pk_cols = $table->primary_key_constraint_column_names; |
1013
|
|
|
|
|
|
|
|
1014
|
0
|
0
|
|
|
|
|
if ("@expected_pk_cols" eq "@pk_cols") { |
1015
|
0
|
|
|
|
|
|
next; |
1016
|
|
|
|
|
|
|
} |
1017
|
|
|
|
|
|
|
|
1018
|
0
|
0
|
|
|
|
|
unless (@pk_cols) { |
1019
|
|
|
|
|
|
|
# If there are no primary keys defined, then treat _all_ the columns |
1020
|
|
|
|
|
|
|
# as primary keys. This means we don't support multiple rows in a |
1021
|
|
|
|
|
|
|
# table containing the same data. |
1022
|
0
|
|
|
|
|
|
@pk_cols = $table->column_names; |
1023
|
|
|
|
|
|
|
} |
1024
|
|
|
|
|
|
|
|
1025
|
0
|
|
|
|
|
|
my %pk_cols; |
1026
|
0
|
|
|
|
|
|
for my $pos (1 .. @pk_cols) { |
1027
|
0
|
|
|
|
|
|
my $pk_col = $pk_cols[$pos-1]; |
1028
|
0
|
0
|
|
|
|
|
my ($property) = grep { defined($_->column_name) and ($_->column_name eq $pk_col) } @properties; |
|
0
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
|
1030
|
0
|
0
|
|
|
|
|
unless ($property) { |
1031
|
|
|
|
|
|
|
# the column has been removed |
1032
|
0
|
|
|
|
|
|
next; |
1033
|
|
|
|
|
|
|
} |
1034
|
0
|
|
|
|
|
|
$pk_cols{$property->property_name} = $pos; |
1035
|
|
|
|
|
|
|
} |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
# all primary key properties are non-nullable, regardless of what the DB allows |
1038
|
0
|
|
|
|
|
|
for my $property (@properties) { |
1039
|
0
|
|
|
|
|
|
my $name = $property->property_name; |
1040
|
0
|
0
|
|
|
|
|
if ($pk_cols{$name}) { |
1041
|
0
|
|
|
|
|
|
$property->is_optional(0); |
1042
|
0
|
|
|
|
|
|
$property->is_id($pk_cols{$name}); |
1043
|
|
|
|
|
|
|
} |
1044
|
|
|
|
|
|
|
} |
1045
|
|
|
|
|
|
|
} # next table (looking just for PK constraint changes) |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
# Make another pass to make sure if a class has a property called 'id' with a column attached, |
1048
|
|
|
|
|
|
|
# then it must be the only ID property of that class |
1049
|
0
|
|
|
|
|
|
my %classes_to_check_id_properties; |
1050
|
0
|
|
|
|
|
|
foreach my $thing ( qw(UR::DataSource::RDBMS::Table UR::DataSource::RDBMS::TableColumn ) ) { |
1051
|
0
|
|
|
|
|
|
foreach my $item ( @{ $dd_changes_by_class{$thing} } ) { |
|
0
|
|
|
|
|
|
|
1052
|
0
|
|
|
|
|
|
my $class_meta = $self->_get_class_meta_for_table_name(data_source => $item->data_source, |
1053
|
|
|
|
|
|
|
table_name => $item->table_name); |
1054
|
0
|
|
0
|
|
|
|
$classes_to_check_id_properties{$class_meta->class_name} ||= $class_meta; |
1055
|
|
|
|
|
|
|
} |
1056
|
|
|
|
|
|
|
} |
1057
|
0
|
|
|
|
|
|
foreach my $class_name ( keys %classes_to_check_id_properties ) { |
1058
|
0
|
|
|
|
|
|
my $class_meta = $classes_to_check_id_properties{$class_name}; |
1059
|
0
|
|
|
|
|
|
my $property_meta = $class_meta->property_meta_for_name('id'); |
1060
|
0
|
0
|
0
|
|
|
|
if ($property_meta && $property_meta->column_name && scalar($class_meta->direct_id_property_metas) > 1) { |
|
|
|
0
|
|
|
|
|
1061
|
0
|
|
|
|
|
|
$self->warning_message("Class $class_name cannot have multiple ID properties when one concrete ID property is named 'id'. It will likely not function correctly unless it is renamed"); |
1062
|
|
|
|
|
|
|
} |
1063
|
0
|
0
|
|
|
|
|
unless (defined $property_meta->is_id) { |
1064
|
0
|
|
|
|
|
|
$self->warning_message("Class $class_name has a property named 'id' that is not an ID property. It will likely not function correctly unless it is renamed"); |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
} |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
|
1070
|
0
|
|
|
|
|
|
$self->status_message("Updating class unique constraints...\n"); |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
# UNIQUE CONSTRAINT / UNIQUE INDEX -> UNIQUE GROUP (loop table objecs since we have no PK DD objects) |
1073
|
0
|
|
|
|
|
|
for my $table (sort $sorter @{ $dd_changes_by_class{'UR::DataSource::RDBMS::Table'} }) { |
|
0
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
# created/updated/unchanged |
1075
|
|
|
|
|
|
|
# delete and re-create |
1076
|
|
|
|
|
|
|
|
1077
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $table->data_source, |
1078
|
|
|
|
|
|
|
table_name => $table->table_name); |
1079
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
1080
|
|
|
|
|
|
|
|
1081
|
0
|
|
|
|
|
|
my @properties = UR::Object::Property->get(class_name => $class_name); |
1082
|
|
|
|
|
|
|
|
1083
|
0
|
|
|
|
|
|
my @uc_names = $table->unique_constraint_names; |
1084
|
0
|
|
|
|
|
|
for my $uc_name (@uc_names) |
1085
|
|
|
|
|
|
|
{ |
1086
|
0
|
|
|
|
|
|
eval { $class->remove_unique_constraint($uc_name) }; |
|
0
|
|
|
|
|
|
|
1087
|
0
|
0
|
|
|
|
|
if ($@ =~ m/There is no constraint named/) { |
|
|
0
|
|
|
|
|
|
1088
|
0
|
|
|
|
|
|
next; # it's OK if there's no UR metadata for this constraint yet |
1089
|
|
|
|
|
|
|
} elsif ($@) { |
1090
|
0
|
|
|
|
|
|
die $@; |
1091
|
|
|
|
|
|
|
} |
1092
|
|
|
|
|
|
|
|
1093
|
0
|
0
|
|
|
|
|
my @uc_cols = map { ref($_) ? @$_ : $_ } $table->unique_constraint_column_names($uc_name); |
|
0
|
|
|
|
|
|
|
1094
|
0
|
|
|
|
|
|
my @uc_property_names; |
1095
|
0
|
|
|
|
|
|
for my $uc_col (@uc_cols) |
1096
|
|
|
|
|
|
|
{ |
1097
|
0
|
0
|
|
|
|
|
my ($property) = grep { defined($_->column_name) and ($_->column_name eq $uc_col) } @properties; |
|
0
|
|
|
|
|
|
|
1098
|
0
|
0
|
|
|
|
|
unless ($property) { |
1099
|
0
|
|
|
|
|
|
$self->warning_message("No property found for column $uc_col for unique constraint $uc_name"); |
1100
|
|
|
|
|
|
|
#$DB::single = 1; |
1101
|
0
|
|
|
|
|
|
next; |
1102
|
|
|
|
|
|
|
} |
1103
|
0
|
|
|
|
|
|
push @uc_property_names, $property->property_name; |
1104
|
|
|
|
|
|
|
} |
1105
|
0
|
|
|
|
|
|
$class->add_unique_constraint($uc_name, @uc_property_names); |
1106
|
|
|
|
|
|
|
} |
1107
|
|
|
|
|
|
|
} # next table (checking separately for unique constraints) |
1108
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
# FK CONSTRAINTS |
1111
|
|
|
|
|
|
|
# These often change name, and as such need to be identified by their actual content. |
1112
|
|
|
|
|
|
|
# Each constraint must match some relationship in the system, or a new one will be added. |
1113
|
|
|
|
|
|
|
|
1114
|
0
|
|
|
|
|
|
$self->status_message("Updating class relationships...\n"); |
1115
|
|
|
|
|
|
|
|
1116
|
0
|
|
|
|
|
|
my $last_class_name = ''; |
1117
|
|
|
|
|
|
|
FK: |
1118
|
0
|
|
|
|
|
|
for my $fk (sort $sorter @{ $dd_changes_by_class{'UR::DataSource::RDBMS::FkConstraint'} }) { |
|
0
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
|
1120
|
0
|
|
|
|
|
|
my $table = $fk->get_table; |
1121
|
0
|
|
|
|
|
|
my $data_source = $fk->data_source; |
1122
|
|
|
|
|
|
|
|
1123
|
0
|
|
|
|
|
|
my $table_name = $fk->table_name; |
1124
|
0
|
|
|
|
|
|
my $r_table_name = $fk->r_table_name; |
1125
|
|
|
|
|
|
|
|
1126
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $data_source, |
1127
|
|
|
|
|
|
|
table_name => $table_name); |
1128
|
0
|
0
|
|
|
|
|
unless ($class) { |
1129
|
0
|
|
|
|
|
|
$self->warning_message( |
1130
|
|
|
|
|
|
|
sprintf("No class found for table for foreign key constraint %-32s %s" . "\n",$table_name, $fk->id) |
1131
|
|
|
|
|
|
|
); |
1132
|
0
|
|
|
|
|
|
next; |
1133
|
|
|
|
|
|
|
} |
1134
|
|
|
|
|
|
|
|
1135
|
0
|
|
|
|
|
|
my $r_class = $self->_get_class_meta_for_table_name(data_source => $data_source, |
1136
|
|
|
|
|
|
|
table_name => $r_table_name); |
1137
|
0
|
0
|
|
|
|
|
unless ($r_class) { |
1138
|
0
|
|
|
|
|
|
$self->warning_message( |
1139
|
|
|
|
|
|
|
sprintf("No class found for r_table for foreign key constraint %-32s %-32s" . "\n",$r_table_name, $fk->id) |
1140
|
|
|
|
|
|
|
); |
1141
|
0
|
|
|
|
|
|
next; |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
|
1144
|
0
|
|
|
|
|
|
my $class_name = $class->class_name; |
1145
|
0
|
|
|
|
|
|
my $r_class_name = $r_class->class_name; |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
# Create an object-accessor property to go with this FK |
1148
|
|
|
|
|
|
|
# First we have to figure out a proper delegation name |
1149
|
|
|
|
|
|
|
# which is a rather convoluted process |
1150
|
|
|
|
|
|
|
|
1151
|
0
|
|
|
|
|
|
my @column_names = $fk->column_names; |
1152
|
0
|
|
|
|
|
|
my @r_column_names = $fk->r_column_names; |
1153
|
0
|
|
|
|
|
|
my (@properties,@property_names,@r_properties,@r_property_names,$prefix,$suffix,$matched); |
1154
|
0
|
|
|
|
|
|
foreach my $i ( 0 .. $#column_names ) { |
1155
|
0
|
|
|
|
|
|
my $column_name = $column_names[$i]; |
1156
|
0
|
|
|
|
|
|
my $property = UR::Object::Property->get( |
1157
|
|
|
|
|
|
|
class_name => $class_name, |
1158
|
|
|
|
|
|
|
column_name => $column_name, |
1159
|
|
|
|
|
|
|
); |
1160
|
0
|
0
|
|
|
|
|
unless ($property) { |
1161
|
0
|
|
|
|
|
|
Carp::confess("Failed to find a property for column $column_name on class $class_name"); |
1162
|
|
|
|
|
|
|
} |
1163
|
0
|
|
|
|
|
|
push @properties,$property; |
1164
|
0
|
|
|
|
|
|
my $property_name = $property->property_name; |
1165
|
0
|
|
|
|
|
|
push @property_names,$property_name; |
1166
|
|
|
|
|
|
|
|
1167
|
0
|
|
|
|
|
|
my $r_column_name = $r_column_names[$i]; |
1168
|
0
|
|
|
|
|
|
my $r_property = UR::Object::Property->get( |
1169
|
|
|
|
|
|
|
class_name => $r_class_name, |
1170
|
|
|
|
|
|
|
column_name => $r_column_name, |
1171
|
|
|
|
|
|
|
); |
1172
|
0
|
0
|
|
|
|
|
unless ($r_property) { |
1173
|
0
|
|
|
|
|
|
Carp::cluck("Failed to find a property for column $r_column_name on class $r_class_name"); |
1174
|
|
|
|
|
|
|
#$DB::single = 1; |
1175
|
0
|
|
|
|
|
|
next FK; |
1176
|
|
|
|
|
|
|
} |
1177
|
0
|
|
|
|
|
|
push @r_properties,$r_property; |
1178
|
0
|
|
|
|
|
|
my $r_property_name = $r_property->property_name; |
1179
|
0
|
|
|
|
|
|
push @r_property_names,$r_property_name; |
1180
|
|
|
|
|
|
|
|
1181
|
0
|
0
|
0
|
|
|
|
if ($property_name =~ /^(.*)$r_property_name(.*)$/ |
1182
|
|
|
|
|
|
|
or $property_name =~ /^(.*)_id$/) { |
1183
|
|
|
|
|
|
|
|
1184
|
0
|
|
|
|
|
|
$prefix = $1; |
1185
|
0
|
0
|
|
|
|
|
$prefix =~ s/_$//g if defined $prefix; |
1186
|
0
|
|
|
|
|
|
$suffix = $2; |
1187
|
0
|
0
|
|
|
|
|
$suffix =~ s/^_//g if defined $suffix; |
1188
|
0
|
|
|
|
|
|
$matched = 1; |
1189
|
|
|
|
|
|
|
} |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
0
|
|
|
|
|
|
my @r_class_name_parts = split('::', $r_class->class_name); |
1193
|
0
|
|
|
|
|
|
shift @r_class_name_parts; # drop the namespace name |
1194
|
0
|
|
|
|
|
|
my $delegation_name = lc(join('_', @r_class_name_parts)); |
1195
|
|
|
|
|
|
|
|
1196
|
0
|
0
|
|
|
|
|
if ($matched) { |
1197
|
0
|
0
|
|
|
|
|
$delegation_name = $delegation_name . "_" . $prefix if $prefix; |
1198
|
0
|
0
|
|
|
|
|
$delegation_name .= ($suffix !~ /\D/ ? "" : "_") . $suffix if $suffix; |
|
|
0
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
} |
1200
|
|
|
|
|
|
|
else { |
1201
|
0
|
|
|
|
|
|
$delegation_name = join("_", @property_names) . "_" . $delegation_name; |
1202
|
|
|
|
|
|
|
} |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
# Generate a delegation name that dosen't conflict with another already in use |
1205
|
0
|
|
|
|
|
|
my %property_names_used = map { $_ => 1 } |
|
0
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
$class->all_property_names; |
1207
|
0
|
|
|
|
|
|
while($property_names_used{$delegation_name}) { |
1208
|
0
|
|
|
|
|
|
$delegation_name =~ /^(.*?)(\d*)$/; |
1209
|
0
|
0
|
|
|
|
|
$delegation_name = $1 . ( ($2 ? $2 : 0) + 1 ); |
1210
|
|
|
|
|
|
|
} |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
# FK columns may have been in an odd order. Get the reference columns in ID order. |
1213
|
0
|
|
|
|
|
|
for my $i (0..$#column_names) |
1214
|
|
|
|
|
|
|
{ |
1215
|
0
|
|
|
|
|
|
my $column_name = $column_names[$i]; |
1216
|
0
|
|
|
|
|
|
my $property = $properties[$i]; |
1217
|
0
|
|
|
|
|
|
my $property_name = $property_names[$i]; |
1218
|
|
|
|
|
|
|
|
1219
|
0
|
|
|
|
|
|
my $r_column_name = $r_column_names[$i]; |
1220
|
0
|
|
|
|
|
|
my $r_property = $r_properties[$i]; |
1221
|
0
|
|
|
|
|
|
my $r_property_name = $r_property_names[$i]; |
1222
|
|
|
|
|
|
|
} |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
# Pick a name that isn't already a property in that class |
1225
|
|
|
|
|
|
|
PICK_A_NAME: |
1226
|
0
|
|
|
|
|
|
for ( 1 ) { |
1227
|
0
|
0
|
|
|
|
|
if (UR::Object::Property->get(class_name => $class_name, |
1228
|
|
|
|
|
|
|
property_name => $delegation_name)) { |
1229
|
0
|
0
|
|
|
|
|
if (UR::Object::Property->get(class_name => $class_name, |
1230
|
|
|
|
|
|
|
property_name => $delegation_name.'_obj')) { |
1231
|
0
|
|
|
|
|
|
foreach my $i ( 1 .. 10 ) { |
1232
|
0
|
0
|
|
|
|
|
unless (UR::Object::Property->get(class_name => $class_name, |
1233
|
|
|
|
|
|
|
property_name => $delegation_name."_$i")) { |
1234
|
0
|
|
|
|
|
|
$delegation_name .= "_$i"; |
1235
|
0
|
|
|
|
|
|
last PICK_A_NAME; |
1236
|
|
|
|
|
|
|
} |
1237
|
|
|
|
|
|
|
} |
1238
|
0
|
|
|
|
|
|
$self->warning_message("Can't generate a relationship property name for $class_name table name $table_name constraint_name ",$fk->fk_constraint_name); |
1239
|
0
|
|
|
|
|
|
next FK; |
1240
|
|
|
|
|
|
|
} else { |
1241
|
0
|
|
|
|
|
|
$delegation_name = $delegation_name.'_obj'; |
1242
|
|
|
|
|
|
|
} |
1243
|
|
|
|
|
|
|
} |
1244
|
|
|
|
|
|
|
} |
1245
|
|
|
|
|
|
|
|
1246
|
0
|
0
|
|
|
|
|
unless ($class->property_meta_for_name($delegation_name)) { |
1247
|
0
|
|
|
|
|
|
my $property = UR::Object::Property->create(class_name => $class_name, |
1248
|
|
|
|
|
|
|
property_name => $delegation_name, |
1249
|
|
|
|
|
|
|
data_type => $r_class_name, |
1250
|
|
|
|
|
|
|
id_by => \@property_names, |
1251
|
|
|
|
|
|
|
constraint_name => $fk->fk_constraint_name, |
1252
|
|
|
|
|
|
|
is_delegated => 1, |
1253
|
|
|
|
|
|
|
is_specified_in_module_header => 1, |
1254
|
|
|
|
|
|
|
); |
1255
|
2
|
|
|
2
|
|
10
|
no warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
1346
|
|
1256
|
0
|
|
|
|
|
|
$self->status_message( |
1257
|
|
|
|
|
|
|
sprintf("A %-40s property %-16s id by %-16s (%s)\n", |
1258
|
|
|
|
|
|
|
$class_name, |
1259
|
|
|
|
|
|
|
$delegation_name, |
1260
|
|
|
|
|
|
|
join(',',@property_names), |
1261
|
|
|
|
|
|
|
$r_class_name |
1262
|
|
|
|
|
|
|
) |
1263
|
|
|
|
|
|
|
); |
1264
|
|
|
|
|
|
|
} |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
} # next fk constraint |
1267
|
|
|
|
|
|
|
|
1268
|
0
|
|
|
|
|
|
return 1; |
1269
|
|
|
|
|
|
|
} |
1270
|
|
|
|
|
|
|
|
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
sub _foreign_key_fingerprint { |
1273
|
0
|
|
|
0
|
|
|
my($self,$fk) = @_; |
1274
|
|
|
|
|
|
|
|
1275
|
0
|
|
|
|
|
|
my $class = $self->_get_class_meta_for_table_name(data_source => $fk->data_source, |
1276
|
|
|
|
|
|
|
table_name => $fk->table_name); |
1277
|
|
|
|
|
|
|
|
1278
|
0
|
|
|
|
|
|
return $class->class_name . ':' . join(',',sort $fk->column_names) . ':' . join(',',sort $fk->r_column_names); |
1279
|
|
|
|
|
|
|
} |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
sub _sync_filesystem { |
1285
|
0
|
|
|
0
|
|
|
my $self = shift; |
1286
|
0
|
|
|
|
|
|
my %params = @_; |
1287
|
|
|
|
|
|
|
|
1288
|
0
|
|
|
|
|
|
my $changed_class_names = delete $params{changed_class_names}; |
1289
|
0
|
0
|
|
|
|
|
if (%params) { |
1290
|
0
|
|
|
|
|
|
Carp::confess("Invalid params passed to _sync_filesystem: " . join(",", keys %params) . "\n"); |
1291
|
|
|
|
|
|
|
} |
1292
|
|
|
|
|
|
|
|
1293
|
0
|
|
|
|
|
|
my $obsolete_module_directory = $self->namespace_name->get_deleted_module_directory_name; |
1294
|
|
|
|
|
|
|
|
1295
|
0
|
|
|
|
|
|
my $namespace = $self->namespace_name; |
1296
|
0
|
|
|
|
|
|
my $no_commit = UR::DBI->no_commit; |
1297
|
0
|
0
|
|
|
|
|
$no_commit = 0 if $self->{'_override_no_commit_for_filesystem_items'}; |
1298
|
|
|
|
|
|
|
|
1299
|
0
|
|
|
|
|
|
for my $class_name (@$changed_class_names) { |
1300
|
0
|
|
|
|
|
|
my $status_message_this_update = ''; |
1301
|
0
|
|
|
|
|
|
my $class_obj; |
1302
|
|
|
|
|
|
|
my $prev; |
1303
|
0
|
0
|
|
|
|
|
if ($class_obj = UR::Object::Type->get(class_name => $class_name)) { |
|
|
0
|
|
|
|
|
|
1304
|
0
|
0
|
0
|
|
|
|
if ($class_obj->{is}[0] =~ /::Type$/ and $class_obj->{is}[0]->isa('UR::Object::Type')) { |
1305
|
0
|
|
|
|
|
|
next; |
1306
|
|
|
|
|
|
|
} |
1307
|
0
|
0
|
|
|
|
|
if ($class_obj->{db_committed}) { |
1308
|
0
|
|
|
|
|
|
$status_message_this_update .= "U " . $class_obj->module_path; |
1309
|
|
|
|
|
|
|
} |
1310
|
|
|
|
|
|
|
else { |
1311
|
0
|
|
|
|
|
|
$status_message_this_update .= "A " . $class_obj->module_path; |
1312
|
|
|
|
|
|
|
} |
1313
|
0
|
0
|
|
|
|
|
$class_obj->rewrite_module_header() unless ($no_commit); |
1314
|
|
|
|
|
|
|
# FIXME A test of automatically making DBIx::Class modules |
1315
|
|
|
|
|
|
|
#$class_obj->dbic_rewrite_module_header() unless ($no_commit); |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
} |
1318
|
|
|
|
|
|
|
elsif ($class_obj = UR::Object::Type::Ghost->get(class_name => $class_name)) { |
1319
|
0
|
0
|
|
|
|
|
if ($class_obj->{is}[0] eq 'UR::Object::Type') { |
1320
|
0
|
|
|
|
|
|
next; |
1321
|
|
|
|
|
|
|
} |
1322
|
|
|
|
|
|
|
|
1323
|
0
|
|
|
|
|
|
$status_message_this_update = "D " . $class_obj->module_path; |
1324
|
|
|
|
|
|
|
|
1325
|
0
|
0
|
|
|
|
|
unless ($no_commit) { |
1326
|
0
|
0
|
|
|
|
|
unless (-d $obsolete_module_directory) { |
1327
|
0
|
|
|
|
|
|
mkdir $obsolete_module_directory; |
1328
|
0
|
0
|
|
|
|
|
unless (-d $obsolete_module_directory) { |
1329
|
0
|
|
|
|
|
|
$self->error_message("Unable to create $obsolete_module_directory for the deleted module for $class_name."); |
1330
|
0
|
|
|
|
|
|
next; |
1331
|
|
|
|
|
|
|
} |
1332
|
|
|
|
|
|
|
} |
1333
|
|
|
|
|
|
|
|
1334
|
0
|
|
|
|
|
|
my $f = IO::File->new($class_obj->module_path); |
1335
|
0
|
|
|
|
|
|
my $old_file_data = join('',$f->getlines); |
1336
|
0
|
|
|
|
|
|
$f->close(); |
1337
|
|
|
|
|
|
|
|
1338
|
0
|
|
|
|
|
|
my $old_module_path = $class_obj->module_path; |
1339
|
0
|
|
|
|
|
|
my $new_module_path = $old_module_path; |
1340
|
0
|
|
|
|
|
|
$new_module_path =~ s/\/$namespace\//\/$namespace\/\.deleted\//; |
1341
|
0
|
|
|
|
|
|
$status_message_this_update .= " (moving $old_module_path to $new_module_path)"; |
1342
|
0
|
|
|
|
|
|
rename $old_module_path, $new_module_path; |
1343
|
|
|
|
|
|
|
|
1344
|
0
|
|
|
|
|
|
UR::Context::Transaction->log_change($class_obj, $class_obj->class_name, $class_obj->id, 'rewrite_module_header', Data::Dumper::Dumper({path => $new_module_path, data => $old_file_data})); |
1345
|
|
|
|
|
|
|
} |
1346
|
|
|
|
|
|
|
} |
1347
|
|
|
|
|
|
|
else { |
1348
|
0
|
|
|
|
|
|
Carp::confess("Failed to find regular or ghost class meta-object for class $class_name!?"); |
1349
|
|
|
|
|
|
|
} |
1350
|
|
|
|
|
|
|
|
1351
|
0
|
0
|
|
|
|
|
if ($no_commit) { |
1352
|
0
|
|
|
|
|
|
$status_message_this_update .= ' (ignored - no-commit)'; |
1353
|
|
|
|
|
|
|
} |
1354
|
0
|
|
|
|
|
|
$self->status_message($status_message_this_update); |
1355
|
|
|
|
|
|
|
|
1356
|
|
|
|
|
|
|
} |
1357
|
|
|
|
|
|
|
|
1358
|
0
|
|
|
|
|
|
return 1; |
1359
|
|
|
|
|
|
|
} |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
1; |
1362
|
|
|
|
|
|
|
|