activefacts-metamodel 1.9.20 → 1.9.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  #