File Coverage

lib/UR.pm
Criterion Covered Total %
statement 26 46 56.5
branch 3 12 25.0
condition 3 9 33.3
subroutine 9 10 90.0
pod n/a
total 41 77 53.2


line stmt bran cond sub pod time code
1             package UR;
2              
3              
4             # The UR module is itself a "UR::Namespace", besides being the root
5             # module which bootstraps the system. The class definition itself
6             # is made at the bottom of the file.
7              
8 266     266   2180501 use strict;
  266         343  
  266         6931  
9 266     266   816 use warnings FATAL => 'all';
  266         954  
  266         12718  
10              
11             # Set the version at compile time, since some other modules borrow it.
12             our $VERSION = "0.46"; # UR $VERSION
13              
14             # Ensure we get detailed errors while starting up.
15             # This is disabled at the bottom of the module.
16 266     266   915 use Carp;
  266         325  
  266         18386  
17             $SIG{__DIE__} = \&Carp::confess;
18              
19             # Ensure that, if the application changes directory, we do not
20             # change where we load modules while running.
21 266     266   983 use Cwd;
  266         323  
  266         118114  
22             my @PERL5LIB = ($ENV{PERL5LIB} ? split(':', $ENV{PERL5LIB}) : ());
23             for my $dir (@INC, @PERL5LIB) {
24             next unless -d $dir;
25             $dir = Cwd::abs_path($dir) || $dir;
26             }
27             $ENV{PERL5LIB} = join(':', @PERL5LIB);
28              
29             # Also need to fix modules that were already loaded, so that when
30             # a namespace is loaded the path will not change out from
31             # underneath it.
32             for my $module (keys %INC) {
33             $INC{$module} = Cwd::abs_path($INC{$module});
34             }
35              
36             # UR supports several environment variables, found under UR/ENV
37             # Any UR_* variable which is set but does NOT corresponde to a module found will cause an exit
38             # (a hedge against typos such as UR_DBI_NO_COMMMMIT=1 leading to unexpected behavior)
39             for my $e (keys %ENV) {
40             next unless substr($e,0,3) eq 'UR_';
41             eval "use UR::Env::$e";
42             if ($@) {
43             my $path = __FILE__;
44             $path =~ s/.pm$//;
45             my @files = glob($path . '/Env/*');
46             my @vars = map { /UR\/Env\/(.*).pm/; $1 } @files;
47             print STDERR "Environment variable $e set to $ENV{$e} but there were errors using UR::Env::$e:\n"
48             . "Available variables:\n\t"
49             . join("\n\t",@vars)
50             . "\n";
51             exit 1;
52             }
53             }
54              
55             # These two dump info about used modules and libraries at program exit.
56             END {
57 266 50   266   11747 if ($ENV{UR_USED_LIBS}) {
58 0         0 print STDERR "Used library include paths (\@INC):\n";
59 0         0 for my $lib (@INC) {
60 0         0 print STDERR "$lib\n";
61             }
62 0         0 print STDERR "\n";
63             }
64 266 50       1043 if ($ENV{UR_USED_MODS}) {
65 0         0 print STDERR "Used modules and paths (\%INC):\n";
66 0         0 for my $mod (sort keys %INC) {
67 0 0       0 if ($ENV{UR_USED_MODS} > 1) {
68 0         0 print STDERR "$mod => $INC{$mod}\n";
69             } else {
70 0         0 print STDERR "$mod\n";
71             }
72             }
73 0         0 print STDERR "\n";
74             }
75 266 50       1012 if ($ENV{UR_DBI_SUMMARIZE_SQL}) {
76 0           UR::DBI::print_sql_summary();
77             }
78             }
79              
80             #Class::AutoloadCAN must be used before Class::Autouse, or the can methods will break in confusing ways.
81 266     266   112741 use Class::AutoloadCAN;
  266         170445  
  266         1248  
82 266     266   135145 use Class::Autouse;
  266         1225705  
  266         3854  
83             BEGIN {
84 266     266   20618 my $v = $Class::Autouse::VERSION;
85 266 0 33     11228 unless (($v =~ /^\d+\.?\d*$/ && $v >= 2.0)
      33        
      33        
86             or $v eq '1.99_02'
87             or $v eq '1.99_04') {
88 0         0 die "UR requires Class::Autouse 2.0 or greater (or 1.99_02 or 1.99_04)!!";
89             }
90             };
91              
92             # Regular deps
93 266     266   115187 use Date::Format;
  266         1463131  
  266         303684  
94              
95             #
96             # Because UR modules execute code when compiling to define their classes,
97             # and require each other for that code to execute, there are bootstrapping
98             # problems.
99             #
100             # Everything which is part of the core framework "requires" UR
101             # which, of course, executes AFTER it has compiled its SUBS,
102             # but BEFORE it defines its class.
103             #
104             # Everything which _uses_ the core of the framework "uses" its namespace,
105             # either the specific top-level namespace module, or "UR" itself for components/extensions.
106             #
107              
108             require UR::Exit;
109             require UR::Util;
110              
111             require UR::DBI::Report; # this is used by UR::DBI
112             require UR::DBI; # this needs a new name, and need only be used by UR::DataSource::RDBMS
113              
114             require UR::ModuleBase; # this should be switched to a role
115             require UR::ModuleConfig; # used by ::Time, and also ::Lock ::Daemon
116              
117             require UR::Object::Iterator;
118             require UR::Context::AutoUnloadPool;
119             require UR::DeletedRef;
120              
121             require UR::Object;
122             require UR::Object::Type;
123              
124             require UR::Object::Ghost;
125             require UR::Object::Property;
126              
127             require UR::Observer;
128              
129             require UR::BoolExpr::Util;
130             require UR::BoolExpr; # has meta
131             require UR::BoolExpr::Template; # has meta
132             require UR::BoolExpr::Template::PropertyComparison; # has meta
133             require UR::BoolExpr::Template::Composite; # has meta
134             require UR::BoolExpr::Template::And; # has meta
135             require UR::BoolExpr::Template::Or; # has meta
136              
137             require UR::Object::Index;
138              
139             #
140             # Define core metadata.
141             #
142             # This is done outside of the actual modules since the define() method
143             # uses all of the modules themselves to do its work.
144             #
145              
146             UR::Object::Type->define(
147             class_name => 'UR::Object',
148             is => [], # the default is to inherit from UR::Object, which is circular, so we explicitly say nothing
149             is_abstract => 1,
150             composite_id_separator => "\t",
151             id_by => [
152             id => { is => 'Scalar', doc => 'unique identifier' }
153             ],
154             id_generator => '-urinternal',
155             );
156              
157             UR::Object::Type->define(
158             class_name => "UR::Object::Index",
159             id_by => ['indexed_class_name','indexed_property_string'],
160             has => ['indexed_class_name','indexed_property_string'],
161             is_transactional => 0,
162             );
163              
164             UR::Object::Type->define(
165             class_name => 'UR::Object::Ghost',
166             is_abstract => 1,
167             );
168              
169             UR::Object::Type->define(
170             class_name => 'UR::Entity',
171             extends => ['UR::Object'],
172             is_abstract => 1,
173             );
174              
175             UR::Object::Type->define(
176             class_name => 'UR::Entity::Ghost',
177             extends => ['UR::Object::Ghost'],
178             is_abstract => 1,
179             );
180              
181             # MORE METADATA CLASSES
182              
183             # For bootstrapping reasons, the properties with default values also need to be listed in
184             # %class_property_defaults defined in UR::Object::Type::Initializer. If you make changes
185             # to default values, please keep these in sync.
186              
187             UR::Object::Type->define(
188             class_name => 'UR::Object::Type',
189             doc => 'class/type meta-objects for UR',
190              
191             id_by => 'class_name',
192             sub_classification_method_name => '_resolve_meta_class_name',
193             is_abstract => 1,
194              
195             has => [
196             class_name => { is => 'Text', len => 256, is_optional => 1,
197             doc => 'the name for the class described' },
198              
199             properties => {
200             is_many => 1,
201              
202             # this is calculated instead of a regular relationship
203             # so we can do appropriate inheritance filtering.
204             # We need an isa operator and its converse
205             # in order to be fully declarative internally here
206             calculate => 'shift->_properties(@_);',
207              
208             doc => 'property meta-objects for the class'
209             },
210             id_properties => { is_many => 1,
211             calculate => q( grep { defined $_->is_id } shift->_properties(@_) ),
212             doc => 'meta-objects for the ID properties of the class' },
213              
214             doc => { is => 'Text', len => 1024, is_optional => 1,
215             doc => 'a one-line description of the class/type' },
216              
217             is_abstract => { is => 'Boolean', default_value => 0,
218             doc => 'abstract classes must be subclassified into a concreate class at create/load time' },
219              
220             is_final => { is => 'Boolean', default_value => 0,
221             doc => 'further subclassification is prohibited on final classes' },
222              
223             is_transactional => { is => 'Boolean', default_value => 1, is_optional => 1,
224             doc => 'non-transactional objects are left out of in-memory transactions' },
225              
226             is_singleton => { is => 'Boolean', default_value => 0,
227             doc => 'singleton classes have only one instance, or have each instance fall into a distinct subclass' },
228              
229             namespace => { is => 'Text', len => 256, is_optional => 1,
230             doc => 'the first "word" in the class name, which points to a UR::Namespace' },
231              
232             schema_name => { is => 'Text', len => 256, is_optional => 1,
233             doc => 'an arbitrary grouping for classes for which instances share a common storage system' },
234              
235             data_source_id => { is => 'Text', len => 256, is_optional => 1,
236             doc => 'for classes which persist beyond their current process, the identifier for their storage manager' },
237              
238             #data_source_meta => { is => 'UR::DataSource', id_by => 'data_source_id', is_optional => 1, },
239              
240             generated => { is => 'Boolean', is_transient => 1, default_value => 0,
241             doc => 'an internal flag set when the class meta has fabricated accessors and methods in the class namespace' },
242              
243             meta_class_name => { is => 'Text',
244             doc => 'even meta-classess have a meta-class' },
245              
246             composite_id_separator => { is => 'Text', len => 2 , default_value => "\t", is_optional => 1,
247             doc => 'for classes whose objects have a multi-value "id", this overrides using a "\t" to compose/decompose' },
248              
249             valid_signals => { is => 'ARRAY', is_optional => 1,
250             doc => 'List of non-standard signal names observers can bind to ' },
251             # details used by the managment of the "real" entity outside of the app (persistence)
252             table_name => { is => 'Text', len => undef, is_optional => 1,
253             doc => 'for classes with a data source, this specifies the table or equivalent data structure which holds instances' },
254              
255             select_hint => { is => 'Text', len => 1024 , is_optional => 1,
256             doc => 'used to optimize access to underlying storage (database specific)' },
257              
258             join_hint => { is => 'Text', len => 1024 , is_optional => 1,
259             doc => 'used to optimize access to underlying storage when this class is part of a join (database specific)' },
260              
261             id_generator => { is => 'Text', len => 256, is_optional => 1,
262             doc => 'override the default choice for generating new object IDs' },
263              
264             # different ways of handling subclassing at object load time
265             subclassify_by => { is => 'Text', len => 256, is_optional => 1,
266             doc => 'when set, the method specified will return the name of a specific subclass into which the object should go' },
267              
268             subclass_description_preprocessor => { is => 'MethodName', len => 255, is_optional => 1,
269             doc => 'a method which should pre-process the class description of sub-classes before construction' },
270              
271             sub_classification_method_name => { is => 'Text', len => 256, is_optional => 1,
272             doc => 'like subclassify_by, but examines whole objects not a single property' },
273              
274             use_parallel_versions => { is => 'Boolean', is_optional => 1, default_value => 0,
275             doc => 'inheriting from the is class will redirect to a ::V? module implemeting a specific version' },
276              
277             # obsolete/internal
278             type_name => { is => 'Text', len => 256, is_deprecated => 1, is_optional => 1 },
279             er_role => { is => 'Text', len => 256, is_optional => 1, default_value => 'entity' },
280             source => { is => 'Text', len => 256 , default_value => 'data dictionary', is_optional => 1 }, # This is obsolete and should be removed later
281             sub_classification_meta_class_name => { is => 'Text', len => 1024 , is_optional => 1,
282             doc => 'obsolete' },
283             first_sub_classification_method_name => { is => 'Text', len => 256, is_optional => 1,
284             doc => 'cached value to handle a complex inheritance hierarchy with storage at some levels but not others' },
285              
286              
287             ### Relationships with the other meta-classes (used internally) ###
288              
289             # UR::Namespaces are singletons referenced through their name
290             namespace_meta => { is => 'UR::Namespace', id_by => 'namespace' },
291             is => { is => 'ARRAY', is_mutable => 0, doc => 'List of the parent class names' },
292             roles => { is => 'ARRAY', is_mutable => 0, is_optional => 1, doc => 'List of the roles consumed by this class' },
293              
294             # linking to the direct parents, and the complete ancestry
295             parent_class_metas => { is => 'UR::Object::Type', id_by => 'is',
296             doc => 'The list of UR::Object::Type objects for the classes that are direct parents of this class' },#, is_many => 1 },
297             parent_class_names => { via => 'parent_class_metas', to => 'class_name', is_many => 1 },
298             parent_meta_class_names => { via => 'parent_class_metas', to => 'meta_class_name', is_many => 1 },
299             ancestry_meta_class_names => { via => 'ancestry_class_metas', to => 'meta_class_name', is_many => 1 },
300             ancestry_class_metas => { is => 'UR::Object::Type', id_by => 'is', where => [-recurse => [class_name => 'is']],
301             doc => 'Climb the ancestry tree and return the class objects for all of them' },
302             ancestry_class_names => { via => 'ancestry_class_metas', to => 'class_name', is_many => 1 },
303              
304             # This one isn't useful on its own, but is used to build the all_* accessors below
305             all_class_metas => { is => 'UR::Object::Type', calculate => 'return ($self, $self->ancestry_class_metas)' },
306              
307             # Properties defined on this class, parent classes, etc.
308             # There's also a property_meta_by_name() method defined in the class
309             direct_property_metas => { is => 'UR::Object::Property', reverse_as => 'class_meta', is_many => 1 },
310             direct_property_names => { via => 'direct_property_metas', to => 'property_name', is_many => 1 },
311             direct_id_property_metas => { is => 'UR::Object::Property', reverse_as => 'class_meta', where => [ 'is_id true' => 1, -order_by => 'is_id' ], is_many => 1 },
312             direct_id_property_names => { via => 'direct_id_property_metas', to => 'property_name', is_many => 1 },
313              
314             ancestry_property_metas => { via => 'ancestry_class_metas', to => 'direct_property_metas', is_many => 1 },
315             ancestry_property_names => { via => 'ancestry_class_metas', to => 'direct_property_names', is_many => 1 },
316             ancestry_id_property_metas => { via => 'ancestry_class_metas', to => 'direct_id_property_metas', is_many => 1 },
317             ancestry_id_property_names => { via => 'ancestry_id_property_metas', to => 'property_name', is_many => 1 },
318              
319             all_property_metas => { via => 'all_class_metas', to => 'direct_property_metas', is_many => 1 },
320             all_property_names => { via => 'all_property_metas', to => 'property_name', is_many => 1 },
321             all_id_property_metas => { via => 'all_class_metas', to => 'direct_id_property_metas', is_many => 1 },
322             all_id_property_names => { via => 'all_id_property_metas', to => 'property_name', is_many => 1 },
323              
324             direct_id_by_property_metas => { via => 'direct_property_metas', to => '__self__', where => ['id_by true' => 1], is_many => 1, doc => "Properties with 'id_by' metadata, ie. direct object accessor properties" } ,
325             all_id_by_property_metas => { via => 'all_class_metas', to => 'direct_id_by_property_metas', is_many => 1},
326             direct_reverse_as_property_metas => { via => 'direct_property_metas', to => '__self__', where => ['reverse_as true' => 1], is_many => 1, doc => "Properties with 'reverse_as' metadata, ie. indirect object accessor properties" },
327             all_reverse_as_property_metas => { via => 'all_class_metas', to => 'direct_reverse_as_property_metas', is_many => 1},
328              
329             # Datasource related stuff
330             direct_column_names => { via => 'direct_property_metas', to => 'column_name', is_many => 1, where => [column_name => { operator => 'true' }] },
331             direct_id_column_names => { via => 'direct_id_property_metas', to => 'column_name', is_many => 1, where => [column_name => { operator => 'true'}] },
332             ancestry_column_names => { via => 'ancestry_class_metas', to => 'direct_column_names', is_many => 1 },
333             ancestry_id_column_names => { via => 'ancestry_class_metas', to => 'direct_id_column_names', is_many => 1 },
334              
335             # Are these *columnless* properties actually necessary? The user could just use direct_property_metas(column_name => undef)
336             direct_columnless_property_metas => { is => 'UR::Object::Property', reverse_as => 'class_meta', where => [column_name => undef], is_many => 1 },
337             direct_columnless_property_names => { via => 'direct_columnless_property_metas', to => 'property_name', is_many => 1 },
338             ancestry_columnless_property_metas => { via => 'ancestry_class_metas', to => 'direct_columnless_property_metas', is_many => 1 },
339             ancestry_columnless_property_names => { via => 'ancestry_columnless_property_metas', to => 'property_name', is_many => 1 },
340             ancestry_table_names => { via => 'ancestry_class_metas', to => 'table_name', is_many => 1 },
341             all_table_names => { via => 'all_class_metas', to => 'table_name', is_many => 1 },
342             all_column_names => { via => 'all_class_metas', to => 'direct_column_names', is_many => 1 },
343             all_id_column_names => { via => 'all_class_metas', to => 'direct_id_column_names', is_many => 1 },
344             all_columnless_property_metas => { via => 'all_class_metas', to => 'direct_columnless_property_metas', is_many => 1 },
345             all_columnless_property_names => { via => 'all_class_metas', to => 'direct_columnless_property_names', is_many => 1 },
346             ],
347             );
348              
349             UR::Object::Type->define(
350             class_name => 'UR::Object::Property',
351             id_properties => [
352             class_name => { is => 'Text', len => 256 },
353             property_name => { is => 'Text', len => 256 },
354             ],
355             has_optional => [
356             property_type => { is => 'Text', len => 256 , is_optional => 1},
357             column_name => { is => 'Text', len => 256, is_optional => 1 },
358             data_length => { is => 'Text', len => 32, is_optional => 1 },
359             data_type => { is => 'Text', len => 256, is_optional => 1 },
360             calculated_default => { is_optional => 1 },
361             default_value => { is_optional => 1 },
362             valid_values => { is => 'ARRAY', is_optional => 1, },
363             example_values => { is => 'ARRAY', is_optional => 1, doc => 'example valid values; used to generate help text for Commands' },
364             doc => { is => 'Text', len => 1000, is_optional => 1 },
365             is_id => { is => 'Integer', default_value => undef, doc => 'denotes this is an ID property of the class, and ranks them' },
366             is_optional => { is => 'Boolean' , default_value => 0},
367             is_transient => { is => 'Boolean' , default_value => 0},
368             is_constant => { is => 'Boolean' , default_value => 0}, # never changes
369             is_mutable => { is => 'Boolean' , default_value => 1}, # can be changed explicitly via accessor (cannot be constant)
370             is_volatile => { is => 'Boolean' , default_value => 0}, # changes w/o a signal: (cannot be constant or transactional)
371             is_classwide => { is => 'Boolean' , default_value => 0},
372             is_delegated => { is => 'Boolean' , default_value => 0},
373             is_calculated => { is => 'Boolean' , default_value => 0},
374             is_transactional => { is => 'Boolean' , default_value => 1}, # STM works on these, and the object can possibly save outside the app
375             is_abstract => { is => 'Boolean' , default_value => 0},
376             is_concrete => { is => 'Boolean' , default_value => 1},
377             is_final => { is => 'Boolean' , default_value => 0},
378             is_many => { is => 'Boolean' , default_value => 0},
379             is_aggregate => { is => 'Boolean' , default_value => 0},
380             is_deprecated => { is => 'Boolean', default_value => 0},
381             is_numeric => { calculate_from => ['data_type'], },
382             id_by => { is => 'ARRAY', is_optional => 1},
383             id_class_by => { is => 'Text', is_optional => 1},
384             is_undocumented => { is => 'Boolean', is_optional => 1, doc => 'do not show in documentation to users' },
385             doc_position => { is => 'Number', is_optional => 1, doc => 'override the sort position within documentation' },
386             access_as => { is => 'Text', is_optional => 1, doc => 'when id_class_by is set, and this is set to "auto", primitives will return as their ID instead of boxed' },
387             order_by => { is => 'ARRAY', is_optional => 1},
388             specify_by => { is => 'Text', is_optional => 1},
389             reverse_as => { is => 'ARRAY', is_optional => 1 },
390             implied_by => { is => 'Text' , is_optional => 1},
391             via => { is => 'Text' , is_optional => 1 },
392             to => { is => 'Text' , is_optional => 1},
393             where => { is => 'ARRAY', is_optional => 1},
394             calculate => { is => 'Text' , is_optional => 1},
395             calculate_from => { is => 'ARRAY' , is_optional => 1},
396             calculate_perl => { is => 'Perl' , is_optional => 1},
397             calculate_sql => { is => 'SQL' , is_optional => 1},
398             calculate_js => { is => 'JavaScript' , is_optional => 1},
399             constraint_name => { is => 'Text' , is_optional => 1},
400             is_legacy_eav => { is => 'Boolean' , is_optional => 1},
401             is_dimension => { is => 'Boolean', is_optional => 1},
402             is_specified_in_module_header => { is => 'Boolean', default_value => 0 },
403             position_in_module_header => { is => 'Integer', is_optional => 1, doc => "Line in the class definition source's section this property appears" },
404             singular_name => { is => 'Text' },
405             plural_name => { is => 'Text' },
406              
407             class_meta => { is => 'UR::Object::Type', id_by => 'class_name' },
408             r_class_meta => { is => 'UR::Object::Type', id_by => 'data_type' },
409             ],
410             unique_constraints => [
411             { properties => [qw/property_name class_name/], sql => 'SUPER_FAKE_O4' },
412             ],
413             );
414              
415              
416             UR::Object::Type->define(
417             class_name => 'UR::Object::Property::Calculated::From',
418             id_properties => [qw/class_name calculated_property_name source_property_name/],
419             );
420              
421             require UR::Singleton;
422             require UR::Namespace;
423              
424             UR::Object::Type->define(
425             class_name => 'UR',
426             extends => ['UR::Namespace'],
427             );
428              
429             require UR::Context;
430             UR::Object::Type->initialize_bootstrap_classes;
431              
432             require UR::Role;
433             require Command;
434              
435             $UR::initialized = 1;
436              
437             require UR::Change;
438             require UR::Context::Root;
439             require UR::Context::Process;
440             require UR::Object::Tag;
441              
442             do {
443             UR::Context->_initialize_for_current_process();
444             };
445              
446             require UR::ModuleLoader; # signs us up with Class::Autouse
447             require UR::Value::Iterator;
448             require UR::Object::View;
449             require UR::Object::Join;
450              
451             sub main::ur_core {
452 0     0     print STDERR "Dumping rules and templates to ./ur_core.stor...\n";
453 0           my $dump;
454 0 0         unless(open($dump, ">ur_core.stor")) {
455 0           print STDERR "Can't open ur_core.stor for writing: $!";
456 0           exit;
457             }
458             store_fd([
459 0           $UR::Object::rule_templates,
460             $UR::Object::rules,
461             ],
462             $dump);
463 0           close $dump;
464 0           exit();
465             }
466              
467             1;
468             __END__