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