activefacts-metamodel 1.9.0 → 1.9.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: afb3d3c2e330c364478dbfa0f3add48f91977a18
4
- data.tar.gz: cae27ec48c4f46e2a0827bbddc20cc1560c997e9
3
+ metadata.gz: 239e2d9123f73a9176d115db67c86c68ed9a6567
4
+ data.tar.gz: 0fee1c5e78e43da17e3c72732efa7de2ac0d7c94
5
5
  SHA512:
6
- metadata.gz: 4a4db7310d00e7fea438ab2edbfa536d5ff994ae736ef491ee58173a5affda389e28a0cad2a7ddfad81d4cd175df1c9078df7a35d01286bb02dbe80086764893
7
- data.tar.gz: 79de78dfc8c6f0ec08c14a7a23940dc5dbc22e422b21614255472bae3449d49411c1d67c14b19cd0c161afc0bae72148c48d12abba990eb081dfbcd827ca1fee
6
+ metadata.gz: 1cf4a18f8354ea5d3f68535c8dc0150da9c82434da62d589a0968e131fcae45108a83ee3dfcfd3e2de05065c45f7045738e597f6a75762ecf859e9f3b977b37b
7
+ data.tar.gz: b7a96af8283b16f3253451b0db7630a13aa51a5a813e24ba97333baa6801a4296198ed5b085a655588335edb26e7a31c63a701880588936e5bc27fd14af2bfa1
data/Gemfile CHANGED
@@ -2,8 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- if ENV['PWD'] =~ %r{\A#{ENV['HOME']}/work}
5
+ if ENV['PWD'] =~ %r{\A#{ENV['HOME']}/work}i
6
6
  $stderr.puts "Using work area gems for #{File.basename(File.dirname(__FILE__))} from activefacts-metamodel"
7
- gem 'activefacts-api', path: '/Users/cjh/work/activefacts/api'
8
- # gem 'activefacts-api', git: 'git://github.com/cjheath/activefacts-api.git'
7
+ gem 'activefacts-api', path: '../api'
9
8
  end
data/Rakefile CHANGED
@@ -23,13 +23,17 @@ task :bump do
23
23
  end
24
24
  end
25
25
 
26
- desc "Generate new CQL from the ORM file"
26
+ desc "Generate new CQL from the ORM file (to verify version similarity)"
27
27
  task :cql do
28
+ # Note: Import from ORM is broken on the implicit derivations in constraints, and the result is unstable.
29
+ # This means that SOME PATCHES WILL FAIL
28
30
  system "afgen --cql orm/Metamodel.orm > Metamodel.cql"
29
- system "afgen --cql cql/Metamodel.cql 2>/dev/null | diff -ub - Metamodel.cql | tee Metamodel.cql.diffs"
31
+ system "patch < orm/Metamodel.cql.diffs"
32
+ system "afgen --cql cql/Metamodel.cql 2>/dev/null >Metamodel.cql.cql"
33
+ system "diff -b -C 1 Metamodel.cql Metamodel.cql.cql | tee Metamodel.cql.diffs"
30
34
  end
31
35
 
32
- desc "Generate new Ruby from the ORM file"
36
+ desc "Generate new Ruby from the CQL file"
33
37
  task :ruby do
34
38
  system %q{
35
39
  afgen --ruby cql/Metamodel.cql 2>/dev/null |
@@ -24,6 +24,6 @@ This gem provides the core representations for the Fact Modeling tools of Active
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
25
  spec.add_development_dependency "rspec", "~> 3.3"
26
26
 
27
- spec.add_runtime_dependency "activefacts-api", ">= 1.9", "~> 1.9.0"
28
- spec.add_development_dependency "activefacts", "~> 1.8", "~> 1.8.0"
27
+ spec.add_runtime_dependency "activefacts-api", ">= 1.8", "~> 1"
28
+ spec.add_development_dependency "activefacts", "~> 1.8", "~> 1"
29
29
  end
data/cql/Metamodel.cql CHANGED
@@ -54,18 +54,6 @@ Coefficient is identified by Numerator and Denominator and Coefficient is precis
54
54
  Coefficient has one Denominator,
55
55
  Coefficient is precise;
56
56
 
57
- Component is identified by Guid where
58
- Component has one Guid,
59
- Guid is of at most one Component;
60
- Component projects at most one Name;
61
- Component has at most one Ordinal rank;
62
-
63
- Composition is identified by Guid where
64
- Composition has one Guid,
65
- Guid is of at most one Composition;
66
- Composition is called one Name,
67
- Name is of at most one Composition;
68
-
69
57
  Concept is identified by Guid where
70
58
  Concept has one Guid,
71
59
  Guid is of at most one Concept;
@@ -88,8 +76,6 @@ Context Note has one Discussion,
88
76
  Context Note applies to at most one relevant-Concept,
89
77
  Concept has Context Note;
90
78
 
91
- Discriminator is a kind of Component;
92
-
93
79
  Enforcement is identified by Constraint where
94
80
  Constraint requires at most one Enforcement,
95
81
  Enforcement applies to one Constraint;
@@ -107,8 +93,6 @@ Fact is of one Fact Type;
107
93
  Implication Rule is identified by its Name;
108
94
  Concept is implied by at most one Implication Rule;
109
95
 
110
- Indicator is a kind of Component;
111
-
112
96
  Instance is identified by Concept where
113
97
  Instance is an instance of one Concept;
114
98
  Instance objectifies at most one Fact,
@@ -120,10 +104,6 @@ Location is identified by X and Y where
120
104
  Location is at one X,
121
105
  Location is at one Y;
122
106
 
123
- Mapping is a kind of Component;
124
- Mapping (as Parent) contains Component (as Member) [acyclic, stronglyintransitive],
125
- Member belongs to at most one Parent;
126
-
127
107
  Presence Constraint is a kind of Constraint;
128
108
  Presence Constraint has at most one max-Frequency restricted to {1..};
129
109
  Presence Constraint has at most one min-Frequency restricted to {2..};
@@ -149,7 +129,7 @@ Role is identified by Fact Type and Ordinal where
149
129
  Fact Type contains Role,
150
130
  Role fills one Ordinal,
151
131
  Ordinal applies to Role;
152
- Indicator indicates one Role played;
132
+
153
133
  Link Fact Type has one implying-Role,
154
134
  implying-Role implies at most one Link Fact Type;
155
135
  Ring Constraint has at most one other-Role,
@@ -176,8 +156,6 @@ Role Value is identified by Fact and Role where
176
156
  Instance plays Role Value,
177
157
  Role Value is of one Instance;
178
158
 
179
- Scoping is a kind of Mapping;
180
-
181
159
  Set Constraint is a kind of Constraint;
182
160
 
183
161
  Shape is identified by Guid where
@@ -253,12 +231,6 @@ Vocabulary contains Constraint,
253
231
  Vocabulary includes Unit,
254
232
  Unit is in one Vocabulary;
255
233
 
256
- Absorption is a kind of Mapping;
257
- Absorption traverses to one child-Role;
258
- Absorption traverses from one parent-Role;
259
- Absorption is matched by at most one reverse-Absorption;
260
- Absorption flattens;
261
-
262
234
  Aggregation is where
263
235
  Variable is bound to Aggregate over aggregated-Variable,
264
236
  Aggregate of aggregated-Variable is bound as one Variable;
@@ -274,12 +246,6 @@ Bound is identified by Value and Bound is inclusive where
274
246
  Value is of at least one Bound,
275
247
  Bound is inclusive;
276
248
 
277
- Composite is identified by Mapping where
278
- Mapping projects at most one Composite,
279
- Composite consists of one Mapping;
280
- Composition contains Composite,
281
- Composite belongs to one Composition;
282
-
283
249
  Constraint Shape is a kind of Shape;
284
250
  Constraint Shape is for one Constraint;
285
251
 
@@ -302,19 +268,12 @@ Diagram is identified by Vocabulary and Name where
302
268
  Diagram is called one Name,
303
269
  Name is of Diagram;
304
270
 
305
- Discriminated Role is where
306
- Discriminator distinguishes Role using one Value,
307
- Role is indicated by Value for Discriminator;
308
-
309
271
  Fact Type Shape is a kind of Shape;
310
272
  Fact Type Shape has at most one Display Role Names Setting;
311
273
  Fact Type Shape is for one Fact Type,
312
274
  Fact Type has Fact Type Shape;
313
275
  Fact Type Shape has at most one Rotation Setting;
314
276
 
315
- Injection is a kind of Mapping;
316
- ValueField is a kind of Injection;
317
-
318
277
  Mirror Role is a kind of Role;
319
278
  Mirror Role is for at most one Role (as Base Role),
320
279
  Base Role implies at most one Mirror Role;
@@ -322,16 +281,6 @@ Mirror Role is for at most one Role (as Base Role),
322
281
  Model Note Shape is a kind of Shape;
323
282
  Model Note Shape is for one Context Note;
324
283
 
325
- Nesting is where
326
- Absorption is nested under index-Role in Ordinal position,
327
- Absorption in Ordinal position is nested under one Role,
328
- Role keys nesting of Absorption at Ordinal priority;
329
- Absorption uses at most one Nesting Mode;
330
- Nesting has at most one key-Name;
331
- Absorption is nested under index Role in Ordinal position
332
- if and only if
333
- that Absorption uses some Nesting Mode;
334
-
335
284
  ORM Diagram is a kind of Diagram;
336
285
  Shape is in one ORM Diagram,
337
286
  ORM Diagram includes Shape;
@@ -340,7 +289,6 @@ Object Type is identified by Vocabulary and Name where
340
289
  Object Type belongs to one Vocabulary,
341
290
  Object Type is called one Name;
342
291
  Instance is of one Object Type;
343
- Mapping represents one Object Type;
344
292
  Object Type is an instance of one Concept;
345
293
  Object Type uses at most one Pronoun;
346
294
  Object Type plays Role,
@@ -457,15 +405,125 @@ Value Type is subtype of at most one super-Value Type (as Supertype) [acyclic, t
457
405
  Supertype is supertype of Value Type;
458
406
 
459
407
  Value Type Parameter is where
460
- Value Type has facet called Name,
461
- Name is a facet of Value Type;
462
- Value Type Parameter requires value of one facet-Value Type;
408
+ Value Type has parameter called Name,
409
+ Name identifies parameter of Value Type;
410
+ Value Type Parameter requires value of one parameter-Value Type;
463
411
 
464
412
  Value Type Parameter Restriction is where
465
413
  Value Type receives Value Type Parameter,
466
414
  Value Type Parameter applies to Value Type;
467
415
  Value Type Parameter Restriction has one Value;
468
416
 
417
+ /* Compositions */
418
+ Composition is identified by Guid where
419
+ Composition has one Guid,
420
+ Guid is of at most one Composition;
421
+ Composition is called one Name,
422
+ Name is of at most one Composition;
423
+
424
+ Component is identified by Guid where
425
+ Component has one Guid,
426
+ Guid is of at most one Component;
427
+ Component projects at most one Name;
428
+ Component has at most one Ordinal rank;
429
+
430
+ Indicator is a kind of Component;
431
+ Indicator indicates one Role played;
432
+
433
+ Discriminator is a kind of Component;
434
+ Discriminated Role is where
435
+ Discriminator distinguishes Role using one Value,
436
+ Role is indicated by Value for Discriminator;
437
+
438
+ Mapping is a kind of Component;
439
+ Mapping represents one Object Type;
440
+ Component (as Member) belongs to at most one Mapping (as Parent),
441
+ Parent contains Member [acyclic, stronglyintransitive];
442
+
443
+ Composite is identified by Mapping where
444
+ Mapping projects at most one Composite,
445
+ Composite consists of one Mapping;
446
+ Composition contains Composite,
447
+ Composite belongs to one Composition;
448
+
449
+ Scoping is a kind of Mapping;
450
+ Injection is a kind of Mapping;
451
+ Value Field is a kind of Injection;
452
+ Surrogate Key is a kind of Injection;
453
+
454
+ Absorption is a kind of Mapping;
455
+ Absorption traverses to one child-Role;
456
+ Absorption traverses from one parent-Role;
457
+ forward-Absorption is matched by at most one reverse-Absorption,
458
+ reverse-Absorption is reverse of at most one forward-Absorption;
459
+ Absorption flattens;
460
+
461
+ some Absorption traverses from some parent- Role(1) and that Role(1) is played by some Object Type
462
+ only if that Absorption is a kind of Mapping(1) that contains some Component that is a Mapping(2) that represents that Object Type;
463
+ some Absorption traverses to some child- Role(1) and that Role(1) is played by some Object Type
464
+ only if that Absorption is a kind of Mapping that represents that Object Type;
465
+
466
+ Full Absorption is where
467
+ Composition fully absorbs Object Type;
468
+ Full Absorption applies to one Absorption,
469
+ Absorption creates at most one Full Absorption;
470
+ /*
471
+ some Object Type is involved in some Full Absorption that applies to some Absorption that is a kind of Mapping
472
+ if and only if
473
+ that Mapping represents that Object Type;
474
+ */
475
+
476
+ /* Access Paths */
477
+ Access Path is identified by Guid where
478
+ Access Path has one Guid,
479
+ Guid is of at most one Access Path;
480
+ Access Path is called at most one Name;
481
+ Access Path is to one Composite,
482
+ Composite is reached through Access Path;
483
+
484
+ /* Indices */
485
+ Index is a kind of Access Path;
486
+ Index is unique;
487
+ Index derives from one Presence Constraint;
488
+ Composite has at most one primary-Index,
489
+ Index is primary for at most one Composite;
490
+ Index is primary for Composite
491
+ only if Index is unique;
492
+ identified Composite has primary Access Path
493
+ only if Composite is reached through Access Path;
494
+
495
+ Index Field is where
496
+ Access Path for Ordinal field uses one Component,
497
+ Component provides Ordinal field for Access Path;
498
+ Index Field is discriminated by at most one Value;
499
+ each Access Path occurs at least one time in
500
+ Access Path for Ordinal field uses Component;
501
+
502
+ /* Foreign keys */
503
+ Foreign Key is a kind of Access Path;
504
+ either Access Path is an Index or Access Path is a Foreign Key but not both;
505
+ Foreign Key traverses from one source-Composite,
506
+ Composite contains Foreign Key;
507
+ Foreign Key derives from one Absorption;
508
+
509
+ Foreign Key Field is where
510
+ Foreign Key for Ordinal field uses one Component,
511
+ Component provides Ordinal field for Foreign Key;
512
+ each Foreign Key occurs at least one time in
513
+ Foreign Key for Ordinal field uses Component;
514
+ Foreign Key Field is discriminated by at most one Value;
515
+
516
+ /* Nesting is a non-first-normal form used for SQL ARRAY and Map (hstore) fields */
517
+ Nesting is where
518
+ Absorption is nested under index-Role in Ordinal position,
519
+ Absorption in Ordinal position is nested under one Role,
520
+ Role keys nesting of Absorption at Ordinal priority;
521
+ Absorption uses at most one Nesting Mode;
522
+ Nesting has at most one key-Name;
523
+ Absorption is nested under index Role in Ordinal position
524
+ if and only if
525
+ that Absorption uses some Nesting Mode;
526
+
469
527
  /*
470
528
  * Constraints:
471
529
  */
@@ -548,8 +606,6 @@ Value Type has Scale
548
606
  only if Value Type has Length;
549
607
  Variable matches nesting over Step
550
608
  only if Variable is for Object Type and Step specifies Fact Type;
551
- each Absorption(1) occurs at most one time in
552
- Absorption(2) is matched by reverse Absorption(1);
553
609
  either Agreement was reached by Agent or Agreement was on Date;
554
610
  // either Component projects Name or Component is some Absorption that flattens;
555
611
  each Concept occurs at most one time in
Binary file
Binary file
@@ -303,14 +303,20 @@ module ActiveFacts
303
303
  rs = rr.role_sequence
304
304
  rs.all_role_ref.size == 1 and
305
305
  rs.all_presence_constraint.detect do |pc|
306
- pc.max_frequency == 1 and !pc.enforcement # Alethic uniqueness constraint
306
+ return pc if pc.max_frequency == 1 and !pc.enforcement # Alethic uniqueness constraint
307
307
  end
308
308
  }
309
+ nil
310
+ end
311
+
312
+ def is_identifying
313
+ uc = uniqueness_constraint and uc.is_preferred_identifier
309
314
  end
310
315
 
311
316
  # Is there are internal uniqueness constraint on this role only?
312
317
  def is_unique
313
- return true if fact_type.is_a?(LinkFactType) # Handle objectification roles
318
+ return true if fact_type.is_a?(LinkFactType) or # Handle objectification roles
319
+ fact_type.all_role.size == 1 # and unary roles
314
320
 
315
321
  uniqueness_constraint ? true : false
316
322
  end
@@ -350,58 +356,8 @@ module ActiveFacts
350
356
  when 2
351
357
  (fact_type.all_role.to_a-[self])[0]
352
358
  else
353
- raise "counterpart roles are undefined in n-ary fact types"
354
- end
355
- end
356
-
357
- def role_type
358
- raise "Incorrectly implemented"
359
- if fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
360
- return object_type == fact_type.supertype ? :supertype : :subtype
359
+ nil # raise "counterpart roles are undefined in n-ary fact types"
361
360
  end
362
-
363
- if fact_type.is_a?(ActiveFacts::Metamodel::LinkFactType)
364
- # Prevent an unnecessary from-1 search:
365
- from_1 = true
366
- # Change the to_1 search to detect a one-to-one:
367
- return fact_type.implying_role.role_type
368
- elsif fact_type.all_role.size == 1
369
- return :unary
370
- end
371
-
372
- # List the UCs on this fact type:
373
- all_uniqueness_constraints =
374
- fact_type.all_role.map do |fact_role|
375
- fact_role.all_role_ref.map do |rr|
376
- rr.role_sequence.all_presence_constraint.select do |pc|
377
- pc.max_frequency == 1
378
- end
379
- end
380
- end.flatten.uniq
381
-
382
- # It's to-1 if a UC exists over exactly this role:
383
- to_1 =
384
- all_uniqueness_constraints.
385
- detect do |c|
386
- (rr = c.role_sequence.all_role_ref.single) and
387
- rr.role == role
388
- end
389
-
390
- if from_1 || fact_type.entity_type
391
- # This is a role in an objectified fact type
392
- from_1 = true
393
- else
394
- # It's from-1 if a UC exists over roles of this FT that doesn't cover this role:
395
- from_1 = all_uniqueness_constraints.detect{|uc|
396
- !uc.role_sequence.all_role_ref.detect{|rr| rr.role == role || rr.role.fact_type != fact_type}
397
- }
398
- end
399
-
400
- if from_1
401
- return to_1 ? :one_one : :one_many
402
- else
403
- return to_1 ? :many_one : :many_many
404
- end
405
361
  end
406
362
  end
407
363
 
@@ -494,6 +450,10 @@ module ActiveFacts
494
450
  def is_separate
495
451
  is_independent or concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
496
452
  end
453
+
454
+ def all_role_transitive
455
+ supertypes_transitive.flat_map(&:all_role)
456
+ end
497
457
  end
498
458
 
499
459
  class ValueType
@@ -501,9 +461,10 @@ module ActiveFacts
501
461
  [self] + (supertype ? supertype.supertypes_transitive : [])
502
462
  end
503
463
 
504
- def subtypes
464
+ def all_subtype
505
465
  all_value_type_as_supertype
506
466
  end
467
+ alias_method :subtypes, :all_subtype # REVISIT: Delete legacy name
507
468
 
508
469
  def subtypes_transitive
509
470
  [self] + subtypes.map{|st| st.subtypes_transitive}.flatten
@@ -516,14 +477,14 @@ module ActiveFacts
516
477
  nil
517
478
  end
518
479
 
519
- # Is this ValueType auto-assigned either at assert or on first save to the database?
480
+ # Is this ValueType auto-assigned? Returns either 'assert', 'commit', otherwise nil.
520
481
  def is_auto_assigned
521
482
  type = self
522
483
  while type
523
- return true if type.name =~ /^Auto/ || type.transaction_phase
484
+ return type.transaction_phase || 'commit' if type.name =~ /^Auto/ || type.transaction_phase
524
485
  type = type.supertype
525
486
  end
526
- false
487
+ nil
527
488
  end
528
489
  end
529
490
 
@@ -534,11 +495,7 @@ module ActiveFacts
534
495
  end
535
496
 
536
497
  def assimilation
537
- if rr = identification_is_inherited
538
- rr.role.fact_type.assimilation
539
- else
540
- nil
541
- end
498
+ ti = identifying_type_inheritance and ti.assimilation
542
499
  end
543
500
 
544
501
  def is_separate
@@ -548,6 +505,11 @@ module ActiveFacts
548
505
  def preferred_identifier
549
506
  return @preferred_identifier if @preferred_identifier
550
507
  if fact_type
508
+ # Objectified unaries are identified by the ID of the object that plays the role:
509
+ if fact_type.all_role.size == 1
510
+ return @preferred_identifier = fact_type.all_role.single.object_type.preferred_identifier
511
+ end
512
+
551
513
  # When compiling a fact instance, the delayed creation of a preferred identifier might be necessary
552
514
  if c = fact_type.check_and_add_spanning_uniqueness_constraint
553
515
  fact_type.check_and_add_spanning_uniqueness_constraint = nil
@@ -606,7 +568,8 @@ module ActiveFacts
606
568
  next unless role.is_unique || fact_type
607
569
  ftroles = Array(role.fact_type.all_role)
608
570
 
609
- # Skip roles in ternary and higher fact types, they're objectified, and in unaries, they can't identify us.
571
+ # Skip roles in ternary and higher fact types, they're objectified
572
+ # REVISIT: This next line prevents a unary being used as a preferred_identifier:
610
573
  next if ftroles.size != 2
611
574
 
612
575
  trace :pi, "Considering role in #{role.fact_type.describe(role)}"
@@ -691,8 +654,8 @@ module ActiveFacts
691
654
  pi = possible_pi
692
655
  end
693
656
  else
694
- debugger
695
657
  trace :pi, "No PI found for #{name}"
658
+ debugger if respond_to?(:debugger)
696
659
  end
697
660
  end
698
661
  raise "No PI found for #{name}" unless pi
@@ -724,13 +687,18 @@ module ActiveFacts
724
687
  }
725
688
  end
726
689
 
727
- # An array all direct supertypes
690
+ # An array of all direct supertypes
728
691
  def supertypes
729
692
  all_supertype_inheritance.map{|ti|
730
693
  ti.supertype
731
694
  }
732
695
  end
733
696
 
697
+ # An array of all direct subtypes
698
+ def all_subtype
699
+ all_type_inheritance_as_supertype.map(&:subtype)
700
+ end
701
+
734
702
  # An array of self followed by all supertypes in order:
735
703
  def supertypes_transitive
736
704
  ([self] + all_type_inheritance_as_subtype.map{|ti|
@@ -738,21 +706,19 @@ module ActiveFacts
738
706
  }).flatten.uniq
739
707
  end
740
708
 
709
+ def identifying_type_inheritance
710
+ all_type_inheritance_as_subtype.detect do |ti|
711
+ ti.provides_identification
712
+ end
713
+ end
714
+
741
715
  # A subtype does not have a identifying_supertype if it defines its own identifier
742
716
  def identifying_supertype
743
- trace "Looking for identifying_supertype of #{name}"
744
- all_type_inheritance_as_subtype.detect{|ti|
745
- trace "considering supertype #{ti.supertype.name}"
746
- next unless ti.provides_identification
747
- trace "found identifying supertype of #{name}, it's #{ti.supertype.name}"
748
- return ti.supertype
749
- }
750
- trace "Failed to find identifying supertype of #{name}"
751
- return nil
717
+ ti = identifying_type_inheritance and ti.supertype
752
718
  end
753
719
 
754
720
  def common_supertype(other)
755
- return nil unless other.is_?(ActiveFacts::Metamodel::EntityType)
721
+ return nil unless other.is_a?(ActiveFacts::Metamodel::EntityType)
756
722
  candidates = supertypes_transitive & other.supertypes_transitive
757
723
  return candidates[0] if candidates.size <= 1
758
724
  candidates[0] # REVISIT: This might not be the closest supertype
@@ -1039,6 +1005,10 @@ module ActiveFacts
1039
1005
  max = max_frequency
1040
1006
  'PresenceConstraint over '+role_sequence.describe + " occurs " + frequency + " time#{(min&&min>1)||(max&&max>1) ? 's' : ''}"
1041
1007
  end
1008
+
1009
+ def covers_role role
1010
+ role_sequence.all_role_ref.map(&:role).include?(role)
1011
+ end
1042
1012
  end
1043
1013
 
1044
1014
  class SubsetConstraint
@@ -1463,6 +1433,112 @@ module ActiveFacts
1463
1433
  end
1464
1434
  end
1465
1435
 
1436
+ class Composite
1437
+ def inspect
1438
+ "Composite #{mapping.inspect}"
1439
+ end
1440
+
1441
+ def show_trace
1442
+ trace :composition, inspect do
1443
+ trace :composition?, "Columns" do
1444
+ mapping.show_trace
1445
+ end
1446
+
1447
+ indices =
1448
+ all_access_path.
1449
+ select{|ap| ap.is_a?(Index)}.
1450
+ sort_by{|ap| [ap.composite_as_primary_index ? 0 : 1] + Array(ap.name)+ap.all_index_field.map(&:inspect) } # REVISIT: Fix hack for stable ordering
1451
+ unless indices.empty?
1452
+ trace :composition, "Indices" do
1453
+ indices.each do |ap|
1454
+ ap.show_trace
1455
+ end
1456
+ end
1457
+ end
1458
+
1459
+ inbound = all_access_path.
1460
+ select{|ap| ap.is_a?(ForeignKey)}.
1461
+ sort_by{|fk| [fk.source_composite.mapping.name, fk.absorption.inspect]+fk.all_foreign_key_field.map(&:inspect)+fk.all_index_field.map(&:inspect) }
1462
+ unless inbound.empty?
1463
+ trace :composition, "Foreign keys inbound" do
1464
+ inbound.each do |fk|
1465
+ fk.show_trace
1466
+ end
1467
+ end
1468
+ end
1469
+
1470
+ outbound =
1471
+ all_foreign_key_as_source_composite.
1472
+ sort_by{|fk| [fk.source_composite.mapping.name, fk.absorption.inspect]+fk.all_index_field.map(&:inspect)+fk.all_foreign_key_field.map(&:inspect) }
1473
+ unless outbound.empty?
1474
+ trace :composition, "Foreign keys outbound" do
1475
+ outbound.each do |fk|
1476
+ fk.show_trace
1477
+ end
1478
+ end
1479
+ end
1480
+ end
1481
+ end
1482
+ end
1483
+
1484
+ class AccessPath
1485
+ def show_trace
1486
+ trace :composition, inspect do
1487
+ if is_a?(ForeignKey)
1488
+ # First list any fields in a foreign key
1489
+ all_foreign_key_field.sort_by(&:ordinal).each do |fk|
1490
+ raise "Internal error: Foreign key not in foreign table!" if fk.component.root != source_composite
1491
+ trace :composition, fk.inspect
1492
+ end
1493
+ end
1494
+ # Now list the fields in the primary key
1495
+ all_index_field.sort_by(&:ordinal).each do |ak|
1496
+ trace :composition, ak.inspect
1497
+ end
1498
+ end
1499
+ end
1500
+ end
1501
+
1502
+ class Index
1503
+ def inspect
1504
+ case
1505
+ when !is_unique
1506
+ 'Non-unique index'
1507
+ when composite_as_primary_index
1508
+ 'Primary index'
1509
+ else
1510
+ 'Unique index'
1511
+ end +
1512
+ (name ? " #{name.inspect}" : '') +
1513
+ " to #{composite.mapping.name}" +
1514
+ (presence_constraint ? " over #{presence_constraint.describe}" : '')
1515
+ end
1516
+ end
1517
+
1518
+ class ForeignKey
1519
+ def inspect
1520
+ "Foreign Key" +
1521
+ (name ? " #{name.inspect}" : '') +
1522
+ " from #{source_composite.mapping.name} to #{composite.mapping.name}" +
1523
+ (absorption ? " over #{absorption.inspect}" : '')
1524
+ end
1525
+ end
1526
+
1527
+ class IndexField
1528
+ def inspect
1529
+ "IndexField part #{ordinal} in #{component.root.mapping.name} references #{component.inspect}" +
1530
+ (value ? " discriminated by #{value.inspect}" : '')
1531
+ end
1532
+ end
1533
+
1534
+ class ForeignKeyField
1535
+ def inspect
1536
+ operation = value ? "filters by value #{value} of" : "is"
1537
+ "ForeignKeyField part #{ordinal} in #{component.root.mapping.name} #{operation} #{component.inspect}" +
1538
+ (value ? " discriminated by #{value.inspect}" : '')
1539
+ end
1540
+ end
1541
+
1466
1542
  class Mapping
1467
1543
  def inspect
1468
1544
  "#{self.class.basename} (#{rank_kind})#{parent ? " in #{parent.name}" :''} of #{name && name != '' ? name : '<anonymous>'}"
@@ -1479,6 +1555,7 @@ module ActiveFacts
1479
1555
 
1480
1556
  # Recompute a contiguous member ranking fron zero, based on current membership:
1481
1557
  def re_rank
1558
+ all_member.each(&:uncache_rank_key)
1482
1559
  next_rank = 0
1483
1560
  all_member.
1484
1561
  sort_by(&:rank_key).
@@ -1488,6 +1565,9 @@ module ActiveFacts
1488
1565
  end
1489
1566
  end
1490
1567
 
1568
+ def root
1569
+ composite || parent && parent.root
1570
+ end
1491
1571
  end
1492
1572
 
1493
1573
  class Nesting
@@ -1504,7 +1584,16 @@ module ActiveFacts
1504
1584
  end
1505
1585
 
1506
1586
  def inspect
1507
- "#{super} in #{inspect_reading}#{absorption ? ' (forward)' : (reverse_absorption ? ' (reverse)' : '')}"
1587
+ "#{super}#{full_absorption ? ' (full)' : ''
1588
+ } in #{inspect_reading}#{
1589
+ # If we have a related forward absorption, we're by definition a reverse absorption
1590
+ if forward_absorption
1591
+ ' (reverse)'
1592
+ else
1593
+ # If we have a related reverse absorption, we're by definition a forward absorption
1594
+ reverse_absorption ? ' (forward)' : ''
1595
+ end
1596
+ }"
1508
1597
  end
1509
1598
 
1510
1599
  def show_trace
@@ -1555,7 +1644,9 @@ module ActiveFacts
1555
1644
  return cvt if pvt != cvt
1556
1645
 
1557
1646
  if !pvt
1558
- # REVISIT: Force the decision if one EntityType identifies another
1647
+ # Force the decision if one EntityType identifies another
1648
+ return true if child_role.base_role.is_identifying # Parent is identified by child role, correct
1649
+ return false if parent_role.base_role.is_identifying # Child is identified by parent role, incorrect
1559
1650
  end
1560
1651
 
1561
1652
  # Primary absorption absorbs the object playing the mandatory role into the non-mandatory:
@@ -1580,14 +1671,15 @@ module ActiveFacts
1580
1671
  end
1581
1672
 
1582
1673
  def flip!
1583
- if (other = absorption)
1674
+ raise "REVISIT: Need to flip FullAbsorption on #{inspect}" if full_absorption or reverse_absorption && reverse_absorption.full_absorption or forward_absorption && forward_absorption.full_absorption
1675
+ if (other = forward_absorption)
1584
1676
  # We point at them - make them point at us instead
1585
- self.absorption = nil
1677
+ self.forward_absorption = nil
1586
1678
  self.reverse_absorption = other
1587
1679
  elsif (other = reverse_absorption)
1588
1680
  # They point at us - make us point at them instead
1589
1681
  self.reverse_absorption = nil
1590
- self.absorption = other
1682
+ self.forward_absorption = other
1591
1683
  else
1592
1684
  raise "Absorption cannot be flipped as it has no reverse"
1593
1685
  end
@@ -1595,6 +1687,12 @@ module ActiveFacts
1595
1687
 
1596
1688
  end
1597
1689
 
1690
+ class FullAbsorption
1691
+ def inspect
1692
+ "Full #{absorption.inspect}"
1693
+ end
1694
+ end
1695
+
1598
1696
  class Indicator
1599
1697
  def inspect
1600
1698
  "#{self.class.basename} #{role.fact_type.default_reading.inspect}"
@@ -1628,22 +1726,34 @@ module ActiveFacts
1628
1726
  class Component
1629
1727
  # The ranking key of a component indicates its importance to its parent:
1630
1728
  # Ranking assigns a total order, but is computed in groups:
1631
- RANK_SUPER = 0 # Supertypes, with the identifying supertype first, others alphabetical
1632
- RANK_IDENT = 1 # Identifying components (absorptions, indicator), in order of the identifier
1633
- RANK_VALUE = 2 # A ValueField
1634
- RANK_INJECTION = 3 # Injections, in alphabetical order
1635
- RANK_DISCRIMINATOR = 4 # Discriminator components, in alphabetical order
1636
- RANK_FOREIGN = 5 # REVISIT: Foreign key components
1637
- RANK_INDICATOR = 6 # Indicators in alphabetical order
1638
- RANK_MANDATORY = 7 # Absorption: unique mandatory
1639
- RANK_NON_MANDATORY = 8 # Absorption: unique optional
1640
- RANK_MULTIPLE = 9 # Absorption: manifold
1641
- RANK_SUBTYPE = 10 # Subtypes in alphabetical order
1642
- RANK_SCOPING = 11 # Scoping in alphabetical order
1729
+ RANK_SURROGATE = 0
1730
+ RANK_SUPER = 1 # Supertypes, with the identifying supertype first, others alphabetical
1731
+ RANK_IDENT = 2 # Identifying components (absorptions, indicator), in order of the identifier
1732
+ RANK_VALUE = 3 # A ValueField
1733
+ RANK_INJECTION = 4 # Injections, in alphabetical order
1734
+ RANK_DISCRIMINATOR = 5 # Discriminator components, in alphabetical order
1735
+ RANK_FOREIGN = 6 # REVISIT: Foreign key components
1736
+ RANK_INDICATOR = 7 # Indicators in alphabetical order
1737
+ RANK_MANDATORY = 8 # Absorption: unique mandatory
1738
+ RANK_NON_MANDATORY = 9 # Absorption: unique optional
1739
+ RANK_MULTIPLE = 10 # Absorption: manifold
1740
+ RANK_SUBTYPE = 11 # Subtypes in alphabetical order
1741
+ RANK_SCOPING = 12 # Scoping in alphabetical order
1742
+
1743
+ def uncache_rank_key
1744
+ @rank_key = nil
1745
+ end
1643
1746
 
1644
1747
  def rank_key
1645
1748
  @rank_key ||=
1646
1749
  case self
1750
+ when SurrogateKey
1751
+ if !parent.parent
1752
+ [RANK_SURROGATE] # an injected PK
1753
+ else
1754
+ [RANK_MANDATORY, name] # an FK
1755
+ end
1756
+
1647
1757
  when Indicator
1648
1758
  if (p = parent_entity_type) and (position = p.rank_in_preferred_identifier(role.base_role))
1649
1759
  [RANK_IDENT, position] # An identifying unary
@@ -1690,6 +1800,15 @@ module ActiveFacts
1690
1800
  end
1691
1801
  end
1692
1802
 
1803
+ def primary_index_components
1804
+ root and
1805
+ ix = root.primary_index and # Primary index has been decided
1806
+ root.primary_index.all_index_field.size > 0 and # has been populated and
1807
+ ix = root.primary_index and
1808
+ ixfs = ix.all_index_field.sort_by(&:ordinal) and
1809
+ ixfs.map(&:component)
1810
+ end
1811
+
1693
1812
  def parent_entity_type
1694
1813
  parent &&
1695
1814
  parent.object_type.is_a?(EntityType) &&
@@ -1699,6 +1818,7 @@ module ActiveFacts
1699
1818
  def rank_kind
1700
1819
  return "top" unless parent # E.g. a Mapping that is a Composite
1701
1820
  case rank_key[0]
1821
+ when RANK_SURROGATE; "surrogate"
1702
1822
  when RANK_SUPER; "supertype"
1703
1823
  when RANK_IDENT; "existential"
1704
1824
  when RANK_VALUE; "self-value"
@@ -1714,6 +1834,14 @@ module ActiveFacts
1714
1834
  end
1715
1835
  end
1716
1836
 
1837
+ def root
1838
+ parent.root
1839
+ end
1840
+
1841
+ def depth
1842
+ parent ? 1+parent.depth : 0
1843
+ end
1844
+
1717
1845
  def inspect
1718
1846
  "#{self.class.basename}"
1719
1847
  end