activefacts-metamodel 1.9.20 → 1.9.22

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: 71dd29d7debbe4133a9425b5582555e6b881b0d5
4
- data.tar.gz: ec5e5d777449f81589ffe5f1210680ff44466a32
3
+ metadata.gz: 8f78f93ff86302b754f0170041f1e5118a08dbbe
4
+ data.tar.gz: d6ade4f75f9960d866088c9c0fc8d6def107bd4e
5
5
  SHA512:
6
- metadata.gz: 4914c1693e41cc07db38f6c6b343783e8c60dc684ece547fe72163d107d5bf47bff0764dd89aea6335544db3e99736d52dc08ae683c35debb15bfa57208582e3
7
- data.tar.gz: 4fe9146122f4b9ba8067a19f95e71ba06fcbfdceb6cfa86ac897ccbc82738585a97073683a67f7279c4ba96b4b77f70f741fc806fb821fd16d6a461be1d597ba
6
+ metadata.gz: 423622558e664532cff04bc8fa9f8ef418e7f0d437894336d6715063744980110c87df94b4308a8aac0908f6c69995d249f970f14c460aaa75722348a18def1a
7
+ data.tar.gz: 837e537f5da6ca45fb8b5590222844e75124a1a80533173773ac3ea7ae05027de4e7b4c193a4a36be4d4f0e08907d445a0a4053f5efae20b88c491b0a25a1933
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ bin
14
14
  *.orig
15
15
  *.diff
16
16
  Metamodel.old.cql
17
+ .byebug_history
@@ -28,6 +28,7 @@ Offset is written as Decimal;
28
28
  Ordinal is written as Unsigned Integer(16);
29
29
  Pronoun is written as String(20) restricted to {'feminine', 'masculine', 'neuter', 'personal'};
30
30
  Regular Expression is written as String;
31
+ Restriction Style is written as String restricted to {'ascending', 'descending', 'fixed'};
31
32
  Ring Type is written as String;
32
33
  Rotation Setting is written as String restricted to {'left', 'right'};
33
34
  Scale is written as Unsigned Integer(32);
@@ -362,12 +363,12 @@ Value Type is subtype of at most one super-Value Type (as Supertype) [acyclic, t
362
363
  Value Type Parameter is where
363
364
  Value Type has parameter called Name,
364
365
  Name identifies parameter of Value Type;
366
+ Value Type Parameter has at most one Restriction Style;
365
367
  Value Type Parameter requires value of one parameter-Value Type;
366
368
 
367
369
  Value Type Parameter Restriction is where
368
- Value Type receives Value Type Parameter,
369
- Value Type Parameter applies to Value Type;
370
- Value Type Parameter Restriction has one Value;
370
+ Value Type allows Value Type Parameter to have Value Range;
371
+
371
372
 
372
373
  /*
373
374
  * Compositions
@@ -387,6 +388,7 @@ Component (as Member) belongs to at most one Component (as Parent) [acyclic, str
387
388
  Component projects at most one Name;
388
389
  Component has at most one Ordinal rank;
389
390
  Component has absolute name;
391
+ Component has at most one injection-Annotation;
390
392
 
391
393
  Indicator is a kind of Component;
392
394
  Indicator indicates one Role played;
@@ -398,9 +400,20 @@ Discriminated Role is where
398
400
  Discriminator distinguishes Role using one Value,
399
401
  Role is indicated by Value for Discriminator;
400
402
 
403
+ Injection is a kind of Component;
404
+ Surrogate Key is a kind of Injection;
405
+ Computed Value is a kind of Injection;
406
+ Hash Value is a kind of Computed Value;
407
+ Component Hash is where
408
+ Hash Value covers at least one Component;
409
+ either Injection is a Surrogate Key
410
+ or Injection is a Computed Value but not both;
411
+
401
412
  Mapping is a kind of Component;
402
413
  Mapping represents one Object Type;
403
414
  Mapping uses at most one native- type Name;
415
+ forward-Mapping is matched by at most one reverse-Mapping,
416
+ reverse-Mapping is reverse of at most one forward-Mapping;
404
417
 
405
418
  Composite is identified by Mapping where
406
419
  Mapping projects at most one Composite,
@@ -411,14 +424,9 @@ Composition contains Composite,
411
424
  each Composite Group is identified by Name;
412
425
  Composite belongs to at most one Composite Group;
413
426
 
414
- Temporal Mapping is a kind of Mapping;
415
- Temporal Mapping records time using one Value Type;
416
-
417
427
  Absorption is a kind of Mapping;
418
428
  Absorption traverses to one child-Role;
419
429
  Absorption traverses from one parent-Role;
420
- forward-Absorption is matched by at most one reverse-Absorption,
421
- reverse-Absorption is reverse of at most one forward-Absorption;
422
430
  Absorption flattens;
423
431
 
424
432
  some Absorption traverses from some parent- Role(1) and that Role(1) is played by some Object Type
@@ -429,8 +437,8 @@ some Absorption traverses to some child- Role(1) and that Role(1) is played by s
429
437
 
430
438
  Full Absorption is where
431
439
  Composition fully absorbs Object Type;
432
- Full Absorption applies to one Absorption,
433
- Absorption creates at most one Full Absorption;
440
+ Full Absorption applies to one Mapping,
441
+ Mapping creates at most one Full Absorption;
434
442
  /*
435
443
  some Object Type is involved in some Full Absorption that applies to some Absorption that is a kind of Mapping
436
444
  if and only if
@@ -453,10 +461,8 @@ Spanning Constraint is where
453
461
  Constraint spans Composite;
454
462
 
455
463
  Scoping is a kind of Mapping;
456
- Injection is a kind of Mapping;
457
- Value Field is a kind of Injection;
458
- Surrogate Key is a kind of Injection;
459
- Valid From is a kind of Injection;
464
+ Value Field is a kind of Mapping;
465
+ Valid From is a kind of Mapping;
460
466
 
461
467
  /* Access Paths */
462
468
  Access Path is identified by Guid where
@@ -469,7 +475,7 @@ Access Path is to one Composite,
469
475
  /* Indices */
470
476
  Index is a kind of Access Path;
471
477
  Index is unique;
472
- Index derives from one Presence Constraint;
478
+ Index derives from at most one Presence Constraint;
473
479
  Composite has at most one natural-Index,
474
480
  Index is natural for at most one Composite;
475
481
  Composite has primary-Index; // Avoid ambiguity; this is a new fact type
@@ -493,8 +499,8 @@ Foreign Key is a kind of Access Path;
493
499
  either Access Path is an Index or Access Path is a Foreign Key but not both;
494
500
  Foreign Key traverses from one source-Composite,
495
501
  Composite contains Foreign Key;
496
- Foreign Key derives from one Absorption,
497
- Absorption gives rise to at most one Foreign Key;
502
+ Foreign Key derives from at most one Mapping,
503
+ Mapping gives rise to at most one Foreign Key;
498
504
 
499
505
  Foreign Key Field is where
500
506
  Foreign Key for Ordinal field uses one Component,
@@ -660,7 +666,8 @@ either Component(1) belongs to Component(2) or Component(1) is a Mapping that pr
660
666
  for each Component exactly one of these holds:
661
667
  Component is a Mapping,
662
668
  Component is an Indicator,
663
- Component is a Discriminator;
669
+ Component is a Discriminator,
670
+ Component is an Injection;
664
671
  for each Concept exactly one of these holds:
665
672
  Object Type is an instance of Concept,
666
673
  Fact Type is an instance of Concept,
@@ -678,11 +685,6 @@ for each Constraint exactly one of these holds:
678
685
  Constraint is a Ring Constraint,
679
686
  Constraint is a Value Constraint;
680
687
  either Domain Object Type is a Value Type or Domain Object Type is an Entity Type but not both;
681
- for each Mapping exactly one of these holds:
682
- Mapping is an Absorption,
683
- Mapping is an Injection,
684
- Mapping is a Scoping,
685
- Mapping projects Composite;
686
688
  for each Role Sequence exactly one of these holds:
687
689
  Role Sequence is for Reading,
688
690
  Presence Constraint covers Role Sequence,
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -25,7 +25,7 @@ module ActiveFacts
25
25
  TYPE_Boolean =>
26
26
  %w{ bit },
27
27
  TYPE_Integer =>
28
- %w{ auto_counter int tiny_int small_int big_int unsigned unsigned_int unsigned_integer signed_int signed_integer},
28
+ %w{ auto_counter int tiny_int small_int big_int long_integer unsigned unsigned_int unsigned_integer signed_int signed_integer},
29
29
  TYPE_Real =>
30
30
  %w{ float double },
31
31
  TYPE_Decimal =>
@@ -47,7 +47,7 @@ module ActiveFacts
47
47
  TYPE_Timestamp =>
48
48
  %w{ time_stamp auto_time_stamp },
49
49
  TYPE_Binary =>
50
- %w{ guid picture_raw_data variable_length_raw_data },
50
+ %w{ binary blob guid picture_raw_data variable_length_raw_data },
51
51
  }
52
52
  TypeParameters = {
53
53
  TYPE_Integer => [:length], # Length is the number of bits
@@ -63,13 +63,17 @@ module ActiveFacts
63
63
  # A DataType Context class should refine this class.
64
64
  # The default context might work for you.
65
65
  class Context
66
+ def initialize options = {}
67
+ raise "Unused options in DataType::Context: #{options.inspect}" unless options.empty?
68
+ end
69
+
66
70
  def integer_ranges
67
71
  end
68
72
 
69
73
  def default_length data_type, type_name
70
74
  end
71
75
 
72
- def choose_integer_type min, max
76
+ def choose_integer_range min, max
73
77
  integer_ranges.
74
78
  select{|type_name, vmin, vmax| min >= vmin && max <= vmax}.
75
79
  sort_by{|type_name, vmin, vmax| vmax-vmin}[0] # Choose the smallest range
@@ -103,7 +107,7 @@ module ActiveFacts
103
107
  when /([a-z ]|\b)Small([a-z ]|\b)/i,
104
108
  /([a-z ]|\b)Short([a-z ]|\b)/i
105
109
  16
106
- when /([a-z ]|\b)Big([a-z ]|\b)/i
110
+ when /([a-z ]|\b)(Big|Long)([a-z ]|\b)/i
107
111
  64
108
112
  else
109
113
  32
@@ -122,12 +126,13 @@ module ActiveFacts
122
126
  end
123
127
  end
124
128
 
125
- def self.normalise type_name
129
+ def self.intrinsic_type type_name
126
130
  data_type, = type_mapping.detect{|t, names| names.detect{|n| n === type_name}}
127
131
  data_type
128
132
  end
129
133
 
130
- def self.normalise_int_length type_name, length = nil, value_constraint = nil, context = DefaultContext.new
134
+ # Integers are available in multiple sizes. Choose the most appropriate one.
135
+ def self.choose_integer type_name, length = nil, value_constraint = nil, context = DefaultContext.new
131
136
  int_length = length || context.default_length(TYPE_Integer, type_name)
132
137
  if int_length
133
138
  if value_constraint
@@ -145,14 +150,18 @@ module ActiveFacts
145
150
  int_max = unsigned ? 2**(int_length-1) - 1 : 2**(int_length-1)-1
146
151
  max = int_max if !max || length && int_max < max
147
152
  end
148
- best = context.choose_integer_type(min, max)
153
+ best = context.choose_integer_range(min, max)
149
154
  unless best || length
150
155
  # Use the largest available integer size
151
156
  best = context.integer_ranges.last
152
157
  end
153
158
 
154
159
  # Use a context-defined integer size if one suits, otherwise the requested size:
155
- best && [best[0], best[3]] || [ 'int', length ]
160
+ if best
161
+ best[0]
162
+ else
163
+ nil # No integer seems suitable
164
+ end
156
165
  end
157
166
 
158
167
  private
@@ -180,6 +189,11 @@ module ActiveFacts
180
189
  primary_index.all_index_field.detect{|ixf| ixf.component == self}
181
190
  end
182
191
 
192
+ def in_natural_index
193
+ natural_index = root.natural_index and
194
+ natural_index.all_index_field.detect{|ixf| ixf.component == self}
195
+ end
196
+
183
197
  def in_foreign_key
184
198
  all_foreign_key_field.detect{|fkf| fkf.foreign_key.source_composite == root}
185
199
  end
@@ -190,17 +204,15 @@ module ActiveFacts
190
204
  context.boolean_type
191
205
 
192
206
  when SurrogateKey
193
- [ context.surrogate_type,
194
- {
195
- auto_assign: ((in_primary_index && !in_foreign_key) ? "commit" : nil),
196
- mandatory: path_mandatory
197
- }
198
- ]
199
-
200
- when ValidFrom
201
- object_type.name
207
+ type_name, options = *context.surrogate_type
208
+ options ||= {}
209
+ # Flag but disable auto-assignment for a surrogate that's an FK (assigned elsewhere)
210
+ options[:auto_assign] ||= 'commit'
211
+ options[:auto_assign] = nil if in_foreign_key
212
+ options[:mandatory] = path_mandatory
213
+ [ type_name, options ]
202
214
 
203
- when ValueField, Absorption
215
+ when ValidFrom, ValueField, Mapping
204
216
  # Walk up the entity type identifiers (must be single-part) to a value type:
205
217
  vt = self.object_type
206
218
  while vt.is_a?(EntityType)
@@ -248,8 +260,6 @@ module ActiveFacts
248
260
  merge!(value_constraint ? {value_constraint: value_constraint} : {})
249
261
  ]
250
262
 
251
- when Injection
252
- object_type.name
253
263
  else
254
264
  raise "Can't make a column from #{component}"
255
265
  end
@@ -265,7 +275,7 @@ end
265
275
 
266
276
  if $0 == __FILE__
267
277
  D = ActiveFacts::Metamodel::DataType
268
- D.normalise('Auto Timestamp')
278
+ D.intrinsic_type('Auto Timestamp')
269
279
 
270
280
  class ModContext < D::DefaultContext
271
281
  def integer_ranges
@@ -277,5 +287,5 @@ if $0 == __FILE__
277
287
  end
278
288
  end
279
289
  puts "Normalising a tiny"
280
- p D.normalise_int_length('tiny', nil, nil, ModContext.new)
290
+ p D.choose_integer('tiny', nil, nil, ModContext.new)
281
291
  end
@@ -1,4 +1,3 @@
1
- require 'pp'
2
1
  #
3
2
  # ActiveFacts Vocabulary Metamodel.
4
3
  # Extensions to the ActiveFacts Vocabulary classes (which are generated from the Metamodel)
@@ -534,6 +533,33 @@ module ActiveFacts
534
533
  end
535
534
  nil
536
535
  end
536
+
537
+ def all_value_type_parameter_transitive
538
+ supertypes_transitive.flat_map{|st| st.all_value_type_parameter.to_a}
539
+ end
540
+
541
+ def applicable_parameter_restrictions parameter_name, include_base_type = false
542
+ vtp = all_value_type_parameter_transitive.detect{|vtp| vtp.name == parameter_name }
543
+ return [] if !vtp || vtp.value_type == self
544
+ vtpr = all_value_type_parameter_restriction.select{|vtpr| vtpr.value_type_parameter == vtp }
545
+ if vtpr.empty?
546
+ supertype.applicable_parameter_restrictions parameter_name
547
+ else
548
+ vtpr
549
+ end
550
+ end
551
+ end
552
+
553
+ class ValueTypeParameter
554
+ def describe
555
+ "ValueType '#{value_type.name}' Parameter '#{name}' is of type '#{parameter_value_type.name}'"
556
+ end
557
+ end
558
+
559
+ class ValueTypeParameterRestriction
560
+ def inspect
561
+ value_range.inspect
562
+ end
537
563
  end
538
564
 
539
565
  class EntityType
@@ -555,101 +581,8 @@ module ActiveFacts
555
581
  assimilation == 'partitioned'
556
582
  end
557
583
 
558
- def new_pi
559
- trace :newpi, "Looking for New PI for #{name}" do
560
- # An objectified fact type is identified by the object which plays it.
561
- if fact_type && fact_type.all_role.size == 1
562
- # REVISIT: If the OFT is separate (is this even possible?), this probably won't work:
563
- return fact_type.all_role.single.object_type.preferred_identifier
564
- end
565
-
566
- # All roles in a preferred identifier must be a counterpart
567
- # to a role played by this type or any of its supertypes.
568
- # Here, a unary role is deemed to be its own counterpart.
569
- # If this is an objectified fact type, its roles are deemed
570
- # to be counterparts per virtue of the mirror role.
571
-
572
- # if name == 'User'
573
- # pp all_role_transitive.map{|a| a.fact_type.describe(a)}
574
- # debugger
575
- # end
576
-
577
- art = all_role_transitive.map do |role|
578
- # No TypeInheritance roles unless we play the subtype
579
- if (ti = role.fact_type).is_a?(TypeInheritance) && role != ti.subtype_role
580
- nil
581
- elsif role.is_a?(MirrorRole)
582
- nil
583
- elsif role.fact_type.all_role.size == 1
584
- role.base_role
585
- else
586
- x = (c = role.counterpart and c.base_role)
587
- # debugger if x && x.object_type == self
588
- x
589
- end
590
- end.compact
591
-
592
- all_role.each do |role|
593
- # Is this the subtype role in a supertype that identifies us?
594
- if (ti = role.base_role.fact_type).is_a?(TypeInheritance)
595
- if role == ti.subtype_role and ti.provides_identification
596
- trace :newpi, "#{name} is identified by supertype #{ti.supertype.name}"
597
-
598
- pcs = ti.supertype_role.all_role_ref.to_a.flat_map(&:role_sequence).flat_map(&:all_presence_constraint).to_a
599
- debugger if pcs.size > 1
600
- return pcs[0]
601
-
602
- return ti.supertype_role.object_type.preferred_identifier
603
- end
604
- next # It can't be this TypeInheritance
605
- end
606
-
607
- # The fact type must be binary or unary
608
- next if role.fact_type.all_role.size > 2
609
-
610
- base_role = role.counterpart.base_role # Go to the OFT role that implied the link fact type
611
- base_role.all_role_ref.each do |rr|
612
- if rr.role_sequence.all_presence_constraint.size > 0
613
- trace :newpi, "Looking for New PI that includes '#{base_role.fact_type.describe(base_role)}'"
614
- end
615
- rr.role_sequence.all_presence_constraint.each do |pc|
616
- next unless pc.max_frequency == 1 and
617
- pc.is_preferred_identifier and
618
- !pc.enforcement # Alethic uniqueness constraint
619
- # We have a uniqueness constraint that is alethic and preferred.
620
- # If all its role are valid counterparts, this is our preferred identifier.
621
- next if pc.role_sequence.all_role_ref.to_a.detect do |nrr|
622
- if !art.include?(nrr.role)
623
- trace :newpi, "Rejecting New PI with excluded role '#{nrr.role.fact_type.describe(nrr.role)}'"
624
- end
625
- !art.include?(nrr.role) # Nope, not included in the valid counterparts
626
- end
627
- trace :newpi, "Accepting New PI #{pc.describe}"
628
- return pc
629
- end
630
- end
631
- end
632
-
633
- debugger
634
- trace :newpi, "No New PI found for #{name}"
635
- nil
636
-
637
- end
638
- end
639
-
640
584
  def preferred_identifier
641
585
  return @preferred_identifier if @preferred_identifier
642
-
643
- @old_preferred_identifier = old_preferred_identifier
644
- # @new_preferred_identifier = new_pi
645
- # if @old_preferred_identifier != @new_preferred_identifier
646
- ## debugger
647
- # new_pi
648
- # end
649
- return @preferred_identifier = @old_preferred_identifier
650
- end
651
-
652
- def old_preferred_identifier
653
586
  if fact_type
654
587
  # Objectified unaries are identified by the ID of the object that plays the role:
655
588
  if fact_type.all_role.size == 1
@@ -1101,11 +1034,17 @@ module ActiveFacts
1101
1034
 
1102
1035
  class AllowedRange
1103
1036
  def to_s(infinity = true)
1104
- min = value_range.minimum_bound
1105
- max = value_range.maximum_bound
1037
+ value_range.to_s infinity
1038
+ end
1039
+ end
1040
+
1041
+ class ValueRange
1042
+ def to_s(infinity = true)
1043
+ min = minimum_bound
1044
+ max = maximum_bound
1106
1045
  # Open-ended string ranges will fail in Ruby
1107
1046
 
1108
- if min = value_range.minimum_bound
1047
+ if min = minimum_bound
1109
1048
  min = min.value
1110
1049
  if min.is_literal_string
1111
1050
  min_literal = min.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
@@ -1115,7 +1054,7 @@ module ActiveFacts
1115
1054
  else
1116
1055
  min_literal = infinity ? "-Infinity" : ""
1117
1056
  end
1118
- if max = value_range.maximum_bound
1057
+ if max = maximum_bound
1119
1058
  max = max.value
1120
1059
  if max.is_literal_string
1121
1060
  max_literal = max.literal.inspect.gsub(/\A"|"\Z/,"'") # Escape string characters
@@ -1129,6 +1068,40 @@ module ActiveFacts
1129
1068
  min_literal +
1130
1069
  (min_literal != (max&&max_literal) ? (".." + max_literal) : "")
1131
1070
  end
1071
+
1072
+ def inspect
1073
+ to_s
1074
+ end
1075
+
1076
+ # Note the mismatched return types here:
1077
+ def effective_maximum
1078
+ maximum_bound ? maximum_bound.value : Infinity
1079
+ end
1080
+
1081
+ def effective_minimum
1082
+ minimum_bound ? minimum_bound.value : -Infinity
1083
+ end
1084
+
1085
+ def includes? range
1086
+ if ValueRange === range
1087
+ range.effective_minimum >= effective_minimum && range.effective_maximum <= effective_maximum
1088
+ # elsif Bound === range # REVISIT: use self.includes?(range.value) with is_literal_string, etc
1089
+ else
1090
+ range >= effective_minimum && range <= effective_maximum
1091
+ end
1092
+ end
1093
+ end
1094
+
1095
+ class Bound
1096
+ def <=(other)
1097
+ return true if other == Infinity
1098
+ is_inclusive ? value <= other : value < other
1099
+ end
1100
+
1101
+ def >=(other)
1102
+ return true if other == -Infinity
1103
+ is_inclusive ? value >= other : value > other
1104
+ end
1132
1105
  end
1133
1106
 
1134
1107
  class Value
@@ -1149,6 +1122,38 @@ module ActiveFacts
1149
1122
  def inspect
1150
1123
  to_s
1151
1124
  end
1125
+
1126
+ def effective_value
1127
+ is_literal_string ? literal : eval(literal)
1128
+ end
1129
+
1130
+ def ==(other)
1131
+ effective_value == (Value === other ? other.effective_value : other)
1132
+ end
1133
+
1134
+ def !=(other)
1135
+ !(self == other)
1136
+ end
1137
+
1138
+ def <=(other)
1139
+ return true if other == Infinity
1140
+ effective_value <= other.effective_value rescue nil
1141
+ end
1142
+
1143
+ def >=(other)
1144
+ return true if other == -Infinity
1145
+ effective_value >= other.effective_value rescue nil
1146
+ end
1147
+
1148
+ def <(other)
1149
+ v = self >= other
1150
+ v == nil ? v : !v
1151
+ end
1152
+
1153
+ def >(other)
1154
+ v = self >= other
1155
+ v == nil ? v : !v
1156
+ end
1152
1157
  end
1153
1158
 
1154
1159
  class PresenceConstraint
@@ -1671,7 +1676,7 @@ module ActiveFacts
1671
1676
 
1672
1677
  inbound = all_access_path.
1673
1678
  select{|ap| ap.is_a?(ForeignKey)}.
1674
- sort_by{|fk| [fk.source_composite.mapping.name, fk.absorption.inspect]+fk.all_foreign_key_field.map(&:inspect)+fk.all_index_field.map(&:inspect) }
1679
+ sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect]+fk.all_foreign_key_field.map(&:inspect)+fk.all_index_field.map(&:inspect) }
1675
1680
  unless inbound.empty?
1676
1681
  trace :composition, "Foreign keys inbound" do
1677
1682
  inbound.each do |fk|
@@ -1682,7 +1687,7 @@ module ActiveFacts
1682
1687
 
1683
1688
  outbound =
1684
1689
  all_foreign_key_as_source_composite.
1685
- sort_by{|fk| [fk.source_composite.mapping.name, fk.absorption.inspect]+fk.all_index_field.map(&:inspect)+fk.all_foreign_key_field.map(&:inspect) }
1690
+ sort_by{|fk| [fk.source_composite.mapping.name, fk.mapping.inspect]+fk.all_index_field.map(&:inspect)+fk.all_foreign_key_field.map(&:inspect) }
1686
1691
  unless outbound.empty?
1687
1692
  trace :composition, "Foreign keys outbound" do
1688
1693
  outbound.each do |fk|
@@ -1697,7 +1702,7 @@ module ActiveFacts
1697
1702
  def all_indices_by_rank
1698
1703
  all_access_path.
1699
1704
  reject{|ap| ap.is_a?(ActiveFacts::Metamodel::ForeignKey)}.
1700
- sort_by{|ap| ap.all_index_field.to_a.flat_map{|ixf| ixf.component.rank_path}.compact }
1705
+ sort_by{|ap| ap.all_index_field.to_a.flat_map{|ixf| (c = ixf.component) ? c.rank_path : 0}.compact }
1701
1706
  end
1702
1707
 
1703
1708
  def all_foreign_key_as_target_composite
@@ -1736,12 +1741,14 @@ module ActiveFacts
1736
1741
  'Non-unique index'
1737
1742
  when composite_as_primary_index
1738
1743
  'Primary index'
1744
+ when composite_as_natural_index
1745
+ 'Natural index'
1739
1746
  else
1740
1747
  'Unique index'
1741
1748
  end +
1742
1749
  (name ? " #{name.inspect}" : '') +
1743
1750
  " to #{composite.mapping.name}" +
1744
- (presence_constraint ? " over #{presence_constraint.describe}" : '')
1751
+ (presence_constraint ? "#{presence_constraint.role_sequence.describe}" : '')
1745
1752
  end
1746
1753
  end
1747
1754
 
@@ -1750,13 +1757,14 @@ module ActiveFacts
1750
1757
  "Foreign Key" +
1751
1758
  (name ? " #{name.inspect}" : '') +
1752
1759
  " from #{(sc = source_composite) ? sc.mapping.name : 'NO-SOURCE'} to #{composite.mapping.name}" +
1753
- (absorption ? " over #{absorption.inspect}" : '')
1760
+ (mapping ? " over #{mapping.inspect}" : '')
1754
1761
  end
1755
1762
  end
1756
1763
 
1757
1764
  class IndexField
1758
1765
  def inspect
1759
- "IndexField part #{ordinal} in #{component.root.mapping.name} references #{component.inspect}" +
1766
+ c_name = component ? component.root.mapping.name : "WARNING: IndexField without Component"
1767
+ "IndexField part #{ordinal} in #{c_name} references #{component.inspect}" +
1760
1768
  (value ? " discriminated by #{value.inspect}" : '')
1761
1769
  end
1762
1770
  end
@@ -1770,6 +1778,10 @@ module ActiveFacts
1770
1778
  end
1771
1779
 
1772
1780
  class Mapping
1781
+ def inspect_reason
1782
+ "#{parent.name} contains mapping of #{name}"
1783
+ end
1784
+
1773
1785
  def inspect
1774
1786
  "#{self.class.basename} (#{rank_kind})#{parent ? " in #{parent.name}" :''} of #{name && name != '' ? name : '<anonymous>'}"
1775
1787
  end
@@ -1777,12 +1789,20 @@ module ActiveFacts
1777
1789
  def show_trace
1778
1790
  trace :composition, "#{ordinal ? "#{ordinal}: " : ''}#{inspect}" do
1779
1791
  yield if block_given?
1792
+ if m = all_member.detect{|m| !m.ordinal}
1793
+ trace :composition, "WARNING: show_trace needed to re_rank #{name} because of unranked #{m.inspect}"
1794
+ re_rank
1795
+ end
1780
1796
  all_member.sort_by{|member| [member.ordinal, member.name]}.each do |member|
1781
1797
  member.show_trace
1782
1798
  end
1783
1799
  end
1784
1800
  end
1785
1801
 
1802
+ def is_type_inheritance
1803
+ false
1804
+ end
1805
+
1786
1806
  # Recompute a contiguous member ranking fron zero, based on current membership:
1787
1807
  def re_rank
1788
1808
  all_member.each(&:uncache_rank_key)
@@ -1815,7 +1835,7 @@ module ActiveFacts
1815
1835
  end
1816
1836
 
1817
1837
  def path_mandatory
1818
- true
1838
+ !parent || parent.path_mandatory
1819
1839
  end
1820
1840
 
1821
1841
  def is_auto_assigned
@@ -1829,6 +1849,10 @@ module ActiveFacts
1829
1849
  nil
1830
1850
  end
1831
1851
  end
1852
+
1853
+ def is_partitioned_here
1854
+ false # Must be an absorption to be partitioned here
1855
+ end
1832
1856
  end
1833
1857
 
1834
1858
  class Nesting
@@ -1840,19 +1864,19 @@ module ActiveFacts
1840
1864
  end
1841
1865
 
1842
1866
  class Absorption
1843
- def inspect_reading
1867
+ def inspect_reason
1844
1868
  parent_role.fact_type.reading_preferably_starting_with_role(parent_role).expand.inspect
1845
1869
  end
1846
1870
 
1847
1871
  def inspect
1848
1872
  "#{super}#{full_absorption ? ' (full)' : ''
1849
- } in #{inspect_reading}#{
1873
+ } in #{inspect_reason}#{
1850
1874
  # If we have a related forward absorption, we're by definition a reverse absorption
1851
- if forward_absorption
1875
+ if forward_mapping
1852
1876
  ' (reverse)'
1853
1877
  else
1854
1878
  # If we have a related reverse absorption, we're by definition a forward absorption
1855
- reverse_absorption ? ' (forward)' : ''
1879
+ reverse_mapping ? ' (forward)' : ''
1856
1880
  end
1857
1881
  }"
1858
1882
  end
@@ -1879,6 +1903,14 @@ module ActiveFacts
1879
1903
  is_type_inheritance && parent_role.fact_type.supertype == object_type
1880
1904
  end
1881
1905
 
1906
+ def is_partitioned_here
1907
+ (ft = child_role.fact_type).is_a?(TypeInheritance) && ft.assimilation == 'partitioned'
1908
+ end
1909
+
1910
+ def is_one_to_one
1911
+ parent_role.is_unique and child_role.is_unique
1912
+ end
1913
+
1882
1914
  def is_preferred_direction
1883
1915
  return child_role.is_mirror_role if child_role.is_mirror_role != parent_role.is_mirror_role
1884
1916
 
@@ -1932,15 +1964,15 @@ module ActiveFacts
1932
1964
  end
1933
1965
 
1934
1966
  def flip!
1935
- 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
1936
- if (other = forward_absorption)
1967
+ raise "REVISIT: Need to flip FullAbsorption on #{inspect}" if full_absorption or reverse_mapping && reverse_mapping.full_absorption or forward_mapping && forward_mapping.full_absorption
1968
+ if (other = forward_mapping)
1937
1969
  # We point at them - make them point at us instead
1938
- self.forward_absorption = nil
1939
- self.reverse_absorption = other
1940
- elsif (other = reverse_absorption)
1970
+ self.forward_mapping = nil
1971
+ self.reverse_mapping = other
1972
+ elsif (other = reverse_mapping)
1941
1973
  # They point at us - make us point at them instead
1942
- self.reverse_absorption = nil
1943
- self.forward_absorption = other
1974
+ self.reverse_mapping = nil
1975
+ self.forward_mapping = other
1944
1976
  else
1945
1977
  raise "Absorption cannot be flipped as it has no reverse"
1946
1978
  end
@@ -2053,8 +2085,10 @@ module ActiveFacts
2053
2085
  when SurrogateKey
2054
2086
  if is_identifying
2055
2087
  [RANK_SURROGATE] # an injected PK
2088
+ elsif injection_annotation
2089
+ [RANK_INJECTION, name]
2056
2090
  else
2057
- [RANK_MANDATORY, name] # an FK
2091
+ [RANK_FOREIGN, name] # an FK
2058
2092
  end
2059
2093
 
2060
2094
  when Indicator
@@ -2070,10 +2104,11 @@ module ActiveFacts
2070
2104
  [RANK_DISCRIMINATOR, name || child_role.name]
2071
2105
 
2072
2106
  when ValueField
2073
- [RANK_IDENT]
2074
-
2075
- when Injection
2076
- [RANK_INJECTION, name] # REVISIT: Injection not fully elaborated. A different sub-key for ranking may be needed
2107
+ if injection_annotation
2108
+ [RANK_INJECTION, name]
2109
+ else
2110
+ [RANK_IDENT]
2111
+ end
2077
2112
 
2078
2113
  when Absorption
2079
2114
  if is_type_inheritance
@@ -2089,6 +2124,8 @@ module ActiveFacts
2089
2124
  end
2090
2125
  elsif (p = parent_entity_type) and (position = p.rank_in_preferred_identifier(child_role.base_role))
2091
2126
  [RANK_IDENT, position]
2127
+ elsif injection_annotation
2128
+ [RANK_INJECTION, name]
2092
2129
  else
2093
2130
  if parent_role.is_unique
2094
2131
  [parent_role.is_mandatory ? RANK_MANDATORY : RANK_NON_MANDATORY, name || child_role.name]
@@ -2100,8 +2137,16 @@ module ActiveFacts
2100
2137
  when Scoping
2101
2138
  [RANK_SCOPING, name || object_type.name]
2102
2139
 
2140
+ when ValidFrom
2141
+ [RANK_INJECTION, name]
2142
+
2103
2143
  when Mapping
2104
- [ name ]
2144
+ # bare Mappings are always injected
2145
+ if root.mapping.object_type == object_type
2146
+ [RANK_IDENT, 0]
2147
+ else
2148
+ [RANK_INJECTION, name]
2149
+ end
2105
2150
 
2106
2151
  else
2107
2152
  raise "unexpected #{self.class.basename} in Component#rank_key"
@@ -2118,6 +2163,10 @@ module ActiveFacts
2118
2163
  ixfs.map(&:component)
2119
2164
  end
2120
2165
 
2166
+ def is_in_primary
2167
+ !!(root and p = root.primary_index and p.all_index_field.detect{|f| f.component == self})
2168
+ end
2169
+
2121
2170
  def parent_entity_type
2122
2171
  parent &&
2123
2172
  parent.object_type.is_a?(EntityType) &&
@@ -2195,7 +2244,7 @@ module ActiveFacts
2195
2244
  end
2196
2245
 
2197
2246
  def fork_to_new_parent parent
2198
- @constellation.fork self, guid: :new, parent: parent
2247
+ @constellation.fork self, guid: :new, parent: parent, injection_annotation: nil
2199
2248
  end
2200
2249
  end
2201
2250
 
@@ -2205,12 +2254,6 @@ module ActiveFacts
2205
2254
  end
2206
2255
  end
2207
2256
 
2208
- class Injection
2209
- def path_mandatory
2210
- parent.path_mandatory
2211
- end
2212
- end
2213
-
2214
2257
  class ValueField
2215
2258
  def inspect
2216
2259
  "#{self.class.basename} #{object_type.name.inspect}"
@@ -2226,7 +2269,7 @@ module ActiveFacts
2226
2269
 
2227
2270
  def fork_to_new_parent parent
2228
2271
  # When we fork from a ValueField, we want to use the name of the ValueType, not the ValueField name
2229
- @constellation.fork self, guid: :new, parent: parent, name: object_type.name
2272
+ @constellation.fork self, guid: :new, parent: parent, name: object_type.name, injection_annotation: nil
2230
2273
  end
2231
2274
  end
2232
2275
 
@@ -2254,6 +2297,14 @@ module ActiveFacts
2254
2297
  end
2255
2298
  (parent ? parent.name + ' ' : '') + 'surrogate key'
2256
2299
  end
2300
+
2301
+ def inspect
2302
+ "#{self.class.basename} (#{rank_kind})#{parent ? " in #{parent.name}" :''} of #{name && name != '' ? name : '<anonymous>'}"
2303
+ end
2304
+
2305
+ def show_trace
2306
+ trace :composition, "#{ordinal ? "#{ordinal}: " : ''}#{inspect}"
2307
+ end
2257
2308
  end
2258
2309
 
2259
2310
  #