| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package UR::Namespace::Command::Update::SchemaDiagram; |
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
24
|
use strict; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
29
|
|
|
7
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
23
|
|
|
8
|
1
|
|
|
1
|
|
4
|
use UR; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
6
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
UR::Object::Type->define( |
|
12
|
|
|
|
|
|
|
class_name => __PACKAGE__, |
|
13
|
|
|
|
|
|
|
is => 'UR::Namespace::Command::Base', |
|
14
|
|
|
|
|
|
|
has => [ |
|
15
|
|
|
|
|
|
|
data_source => {type => 'String', doc => 'Which datasource to use', is_optional => 1}, |
|
16
|
|
|
|
|
|
|
depth => { type => 'Integer', doc => 'Max distance of related tables to include. Default is 1. 0 means show only the named tables, -1 means to include everything', is_optional => 1}, |
|
17
|
|
|
|
|
|
|
file => { type => 'String', doc => 'Pathname of the Umlet (.uxf) file' }, |
|
18
|
|
|
|
|
|
|
show_columns => { type => 'Boolean', is_optional => 1, default => 1, doc => 'Include column names in the diagram' }, |
|
19
|
|
|
|
|
|
|
initial_name => { |
|
20
|
|
|
|
|
|
|
is_many => 1, |
|
21
|
|
|
|
|
|
|
is_optional => 1, |
|
22
|
|
|
|
|
|
|
shell_args_position => 1 |
|
23
|
|
|
|
|
|
|
} |
|
24
|
|
|
|
|
|
|
], |
|
25
|
|
|
|
|
|
|
); |
|
26
|
|
|
|
|
|
|
|
|
27
|
0
|
|
|
0
|
0
|
|
sub sub_command_sort_position { 3 }; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub help_brief { |
|
31
|
0
|
|
|
0
|
0
|
|
"Update an Umlet diagram based on the current schema" |
|
32
|
|
|
|
|
|
|
} |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub help_detail { |
|
35
|
0
|
|
|
0
|
0
|
|
return <
|
|
36
|
|
|
|
|
|
|
Creates a new Umlet diagram, or updates an existing diagram. Bare arguments |
|
37
|
|
|
|
|
|
|
are taken as table names to include in the diagram. Other tables may be |
|
38
|
|
|
|
|
|
|
included in the diagram based on their distance from the names tables |
|
39
|
|
|
|
|
|
|
and the --depth parameter. |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
If an existing file is being updated, the position of existing elements |
|
42
|
|
|
|
|
|
|
will not change. |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
EOS |
|
45
|
|
|
|
|
|
|
} |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# The max X coord to use when placing boxes. After this, move down a line and go back to the left |
|
48
|
1
|
|
|
1
|
|
4
|
use constant MAX_X_AUTO_POSITION => 1000; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
921
|
|
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# FIXME This execute() and the one from ur update class-diagram should be combined since they share |
|
51
|
|
|
|
|
|
|
# most of the code |
|
52
|
|
|
|
|
|
|
sub execute { |
|
53
|
0
|
|
|
0
|
|
|
my $self = shift; |
|
54
|
|
|
|
|
|
|
|
|
55
|
0
|
|
|
|
|
|
my $params = shift; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
#$DB::single = 1; |
|
58
|
0
|
|
|
|
|
|
my $namespace = $self->namespace_name; |
|
59
|
0
|
|
|
|
|
|
eval "use $namespace"; |
|
60
|
0
|
0
|
|
|
|
|
if ($@) { |
|
61
|
0
|
|
|
|
|
|
$self->error_message("Failed to load module for $namespace: $@"); |
|
62
|
0
|
|
|
|
|
|
return; |
|
63
|
|
|
|
|
|
|
} |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# FIXME this is a workaround for a bug. If you try to get Table objects filtered by namespace, |
|
67
|
|
|
|
|
|
|
# you have to have already instantiated the namespace's data source objects into the object cache |
|
68
|
|
|
|
|
|
|
# first |
|
69
|
0
|
|
|
|
|
|
map { $_->_singleton_object } $namespace->get_data_sources; |
|
|
0
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
|
|
71
|
0
|
|
|
|
|
|
my @initial_name_list; |
|
72
|
0
|
0
|
|
|
|
|
if ($params->{'depth'} == -1) { |
|
73
|
|
|
|
|
|
|
# They wanted them all... Ignore whatever is on the command line |
|
74
|
0
|
|
|
|
|
|
@initial_name_list = map { $_->table_name} |
|
|
0
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
UR::DataSource::RDBMS::Table->get(namespace => $namespace); |
|
76
|
|
|
|
|
|
|
} else { |
|
77
|
0
|
|
|
|
|
|
@initial_name_list = $self->initial_name; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
|
|
80
|
0
|
|
|
|
|
|
my $diagram; |
|
81
|
0
|
0
|
|
|
|
|
if (-f $params->{'file'}) { |
|
82
|
0
|
0
|
|
|
|
|
$params->{'depth'} = 0 unless (exists $params->{'depth'}); # Default is just update what's there |
|
83
|
0
|
|
|
|
|
|
$diagram = UR::Object::Umlet::Diagram->create_from_file($params->{'file'}); |
|
84
|
0
|
|
|
|
|
|
push @initial_name_list, map { $_->subject_id } UR::Object::Umlet::Class->get(diagram_name => $diagram->name); |
|
|
0
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
} else { |
|
86
|
0
|
0
|
|
|
|
|
$params->{'depth'} = 1 unless exists($params->{'depth'}); |
|
87
|
0
|
|
|
|
|
|
$diagram = UR::Object::Umlet::Diagram->create(name => $params->{'file'}); |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# FIXME this can get removed when attribute defaults work correctly |
|
92
|
0
|
0
|
|
|
|
|
unless (exists $params->{'show_attributes'}) { |
|
93
|
0
|
|
|
|
|
|
$self->show_columns(1); |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
|
my @involved_tables = map { UR::DataSource::RDBMS::Table->get(table_name => $_, namespace => $namespace) } |
|
|
0
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
@initial_name_list; |
|
98
|
|
|
|
|
|
|
#foreach my $table_name ( @initial_name_list ) { |
|
99
|
|
|
|
|
|
|
# # FIXME namespace dosen't work here either |
|
100
|
|
|
|
|
|
|
# push @involved_tables, UR::DataSource::RDBMS::Table->get(namespace => $namespace, table_name => $table_name); |
|
101
|
|
|
|
|
|
|
#} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
#$DB::single = 1; |
|
104
|
|
|
|
|
|
|
push @involved_tables ,$self->_get_related_items( names => \@initial_name_list, |
|
105
|
0
|
|
|
|
|
|
depth => $params->{'depth'}, |
|
106
|
|
|
|
|
|
|
namespace => $namespace, |
|
107
|
|
|
|
|
|
|
item_class => 'UR::DataSource::RDBMS::Table', |
|
108
|
|
|
|
|
|
|
item_param => 'table_name', |
|
109
|
|
|
|
|
|
|
related_class => 'UR::DataSource::RDBMS::FkConstraint', |
|
110
|
|
|
|
|
|
|
related_param => 'r_table_name', |
|
111
|
|
|
|
|
|
|
); |
|
112
|
0
|
|
|
|
|
|
my %involved_table_names = map { $_->table_name => 1 } @involved_tables; |
|
|
0
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# Figure out the initial placement |
|
115
|
|
|
|
|
|
|
# The initial placement, and how much to move over for the next box |
|
116
|
0
|
|
|
|
|
|
my($x_coord, $y_coord, $x_inc, $y_inc) = (20,20,40,40); |
|
117
|
0
|
0
|
|
|
|
|
my @objs = sort { $b->y <=> $a->y or $b->x <=> $a->x } UR::Object::Umlet::Class->get(); |
|
|
0
|
|
|
|
|
|
|
|
118
|
0
|
0
|
|
|
|
|
if (@objs) { |
|
119
|
0
|
|
|
|
|
|
my $maxobj = $objs[0]; |
|
120
|
0
|
|
|
|
|
|
$x_coord = $maxobj->x + $maxobj->width + $x_inc; |
|
121
|
0
|
|
|
|
|
|
$y_coord = $maxobj->y + $maxobj->height + $y_inc; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# First, place all the tables' boxes |
|
126
|
0
|
|
|
|
|
|
my @all_boxes = UR::Object::Umlet::Class->get( diagram_name => $diagram->name ); |
|
127
|
0
|
|
|
|
|
|
foreach my $table ( @involved_tables ) { |
|
128
|
0
|
|
|
|
|
|
my $umlet_table = UR::Object::Umlet::Class->get(diagram_name => $diagram->name, |
|
129
|
|
|
|
|
|
|
subject_id => $table->table_name); |
|
130
|
0
|
|
|
|
|
|
my $created = 0; |
|
131
|
0
|
0
|
|
|
|
|
unless ($umlet_table) { |
|
132
|
0
|
|
|
|
|
|
$created = 1; |
|
133
|
0
|
|
|
|
|
|
$umlet_table = UR::Object::Umlet::Class->create( diagram_name => $diagram->name, |
|
134
|
|
|
|
|
|
|
subject_id => $table->table_name, |
|
135
|
|
|
|
|
|
|
label => $table->table_name, |
|
136
|
|
|
|
|
|
|
x => $x_coord, |
|
137
|
|
|
|
|
|
|
y => $y_coord, |
|
138
|
|
|
|
|
|
|
); |
|
139
|
|
|
|
|
|
|
|
|
140
|
0
|
0
|
|
|
|
|
if ($self->show_columns) { |
|
141
|
0
|
|
0
|
|
|
|
my $attributes = $umlet_table->attributes || []; |
|
142
|
0
|
|
|
|
|
|
my %attributes_already_in_diagram = map { $_->{'name'} => 1 } @{ $attributes }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
143
|
0
|
|
|
|
|
|
my %pk_properties = map { $_ => 1 } $table->primary_key_constraint_column_names; |
|
|
0
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
my $line_count = scalar @$attributes; |
|
146
|
0
|
|
|
|
|
|
foreach my $column_name ( $table->column_names ) { |
|
147
|
0
|
0
|
|
|
|
|
next if $attributes_already_in_diagram{$column_name}; |
|
148
|
0
|
|
|
|
|
|
$line_count++; |
|
149
|
0
|
|
|
|
|
|
my $column = UR::DataSource::RDBMS::TableColumn->get(table_name => $table->table_name, |
|
150
|
|
|
|
|
|
|
column_name => $column_name, |
|
151
|
|
|
|
|
|
|
namespace => $namespace); |
|
152
|
0
|
0
|
|
|
|
|
push @$attributes, { is_id => $pk_properties{$column_name} ? '+' : ' ', |
|
153
|
|
|
|
|
|
|
name => $column_name, |
|
154
|
|
|
|
|
|
|
type => $column->data_type, |
|
155
|
|
|
|
|
|
|
line => $line_count, |
|
156
|
|
|
|
|
|
|
}; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
0
|
|
|
|
|
|
$umlet_table->attributes($attributes); |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Make sure this box dosen't overlap other boxes |
|
162
|
0
|
|
|
|
|
|
while(my $overlapped = $umlet_table->is_overlapping(@all_boxes) ) { |
|
163
|
0
|
0
|
|
|
|
|
if ($umlet_table->x > MAX_X_AUTO_POSITION) { |
|
164
|
0
|
|
|
|
|
|
$umlet_table->x(20); |
|
165
|
0
|
|
|
|
|
|
$umlet_table->y( $umlet_table->y + $y_inc); |
|
166
|
|
|
|
|
|
|
} else { |
|
167
|
0
|
|
|
|
|
|
$umlet_table->x( $overlapped->x + $overlapped->width + $x_inc ); |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
|
push @all_boxes, $umlet_table; |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
|
|
174
|
0
|
0
|
|
|
|
|
if ($created) { |
|
175
|
0
|
|
|
|
|
|
$x_coord = $umlet_table->x + $umlet_table->width + $x_inc; |
|
176
|
0
|
0
|
|
|
|
|
if ($x_coord > MAX_X_AUTO_POSITION) { |
|
177
|
0
|
|
|
|
|
|
$x_coord = 20; |
|
178
|
0
|
|
|
|
|
|
$y_coord += $y_inc; |
|
179
|
|
|
|
|
|
|
} |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# Next, connect the tables together |
|
184
|
0
|
|
|
|
|
|
foreach my $table ( @involved_tables ) { |
|
185
|
0
|
|
|
|
|
|
foreach my $fk ( UR::DataSource::RDBMS::FkConstraint->get(table_name => $table->table_name, namespace => $namespace) ) { |
|
186
|
|
|
|
|
|
|
|
|
187
|
0
|
0
|
|
|
|
|
next unless ($involved_table_names{$fk->r_table_name}); |
|
188
|
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
my $umlet_relation = UR::Object::Umlet::Relation->get( #diagram_name => $diagram->name, |
|
190
|
|
|
|
|
|
|
from_entity_name => $fk->table_name, |
|
191
|
|
|
|
|
|
|
to_entity_name => $fk->r_table_name, |
|
192
|
|
|
|
|
|
|
); |
|
193
|
0
|
0
|
|
|
|
|
unless ($umlet_relation) { |
|
194
|
0
|
|
|
|
|
|
my @fk_column_names = $fk->column_name_map(); |
|
195
|
0
|
|
|
|
|
|
my $label = join("\n", map { $_->[0] . " -> " . $_->[1] } @fk_column_names); |
|
|
0
|
|
|
|
|
|
|
|
196
|
0
|
|
|
|
|
|
$umlet_relation = UR::Object::Umlet::Relation->create( diagram_name => $diagram->name, |
|
197
|
|
|
|
|
|
|
relation_type => '<-', |
|
198
|
|
|
|
|
|
|
label => $label, |
|
199
|
|
|
|
|
|
|
from_entity_name => $fk->table_name, |
|
200
|
|
|
|
|
|
|
to_entity_name => $fk->r_table_name, |
|
201
|
|
|
|
|
|
|
); |
|
202
|
0
|
|
|
|
|
|
$umlet_relation->connect_entities(); |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
} |
|
206
|
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
|
$diagram->save_to_file($params->{'file'}); |
|
208
|
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
|
1; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub _get_related_items { |
|
214
|
0
|
|
|
0
|
|
|
my($self, %params) = @_; |
|
215
|
|
|
|
|
|
|
|
|
216
|
0
|
0
|
|
|
|
|
return unless (@{$params{'names'}}); |
|
|
0
|
|
|
|
|
|
|
|
217
|
0
|
0
|
|
|
|
|
return unless $params{'depth'}; |
|
218
|
|
|
|
|
|
|
|
|
219
|
0
|
|
|
|
|
|
my $item_class = $params{'item_class'}; |
|
220
|
0
|
|
|
|
|
|
my $item_param = $params{'item_param'}; |
|
221
|
|
|
|
|
|
|
|
|
222
|
0
|
|
|
|
|
|
my $related_class = $params{'related_class'}; |
|
223
|
0
|
|
|
|
|
|
my $related_param = $params{'related_param'}; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# Get everything linked to the named things |
|
226
|
0
|
|
|
|
|
|
my @related_names = map { $_->$related_param } $related_class->get($item_param => $params{'names'}, namespace => $params{'namespace'}); |
|
|
0
|
|
|
|
|
|
|
|
227
|
0
|
|
|
|
|
|
push @related_names, map { $_->$item_param } $related_class->get($related_param => $params{'names'}, namespace => $params{'namespace'}); |
|
|
0
|
|
|
|
|
|
|
|
228
|
0
|
0
|
|
|
|
|
return unless @related_names; |
|
229
|
|
|
|
|
|
|
|
|
230
|
0
|
|
|
|
|
|
my @objs = $item_class->get($item_param => \@related_names, namespace => $params{'namespace'}); |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# make a recursive call to get the related objects by name |
|
233
|
0
|
|
|
|
|
|
return ( @objs, $self->_get_related_items( %params, names => \@related_names, depth => --$params{'depth'}) ); |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
1; |
|
240
|
|
|
|
|
|
|
|