File Coverage

lib/UR/Namespace/Command/Update/SchemaDiagram.pm
Criterion Covered Total %
statement 12 110 10.9
branch 0 40 0.0
condition 0 2 0.0
subroutine 4 9 44.4
pod 0 3 0.0
total 16 164 9.7


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