activefacts-compositions 1.9.17 → 1.9.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/activefacts-compositions.gemspec +2 -2
  3. data/lib/activefacts/compositions/binary.rb +1 -1
  4. data/lib/activefacts/compositions/compositor.rb +16 -12
  5. data/lib/activefacts/compositions/datavault.rb +110 -115
  6. data/lib/activefacts/compositions/relational.rb +137 -94
  7. data/lib/activefacts/compositions/staging.rb +89 -27
  8. data/lib/activefacts/compositions/traits/datavault.rb +116 -49
  9. data/lib/activefacts/compositions/traits/rails.rb +2 -2
  10. data/lib/activefacts/compositions/version.rb +1 -1
  11. data/lib/activefacts/generator/doc/cwm.rb +6 -18
  12. data/lib/activefacts/generator/doc/ldm.rb +1 -1
  13. data/lib/activefacts/generator/etl/unidex.rb +341 -0
  14. data/lib/activefacts/generator/oo.rb +31 -14
  15. data/lib/activefacts/generator/rails/models.rb +6 -5
  16. data/lib/activefacts/generator/rails/schema.rb +5 -9
  17. data/lib/activefacts/generator/ruby.rb +2 -2
  18. data/lib/activefacts/generator/sql/mysql.rb +3 -184
  19. data/lib/activefacts/generator/sql/oracle.rb +3 -152
  20. data/lib/activefacts/generator/sql/postgres.rb +3 -145
  21. data/lib/activefacts/generator/sql/server.rb +3 -126
  22. data/lib/activefacts/generator/sql.rb +54 -422
  23. data/lib/activefacts/generator/summary.rb +15 -6
  24. data/lib/activefacts/generator/traits/expr.rb +41 -0
  25. data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
  26. data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
  27. data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
  28. data/lib/activefacts/generator/traits/sql/server.rb +262 -0
  29. data/lib/activefacts/generator/traits/sql.rb +538 -0
  30. metadata +13 -8
  31. data/lib/activefacts/compositions/docgraph.rb +0 -798
  32. data/lib/activefacts/compositions/staging/persistent.rb +0 -107
@@ -14,13 +14,31 @@ module ActiveFacts
14
14
 
15
15
  def self.options
16
16
  {
17
- surrogates: ['Boolean', "Inject a surrogate key into each table whose primary key is not already suitable as a foreign key"],
17
+ surrogates: [%w{true field_name_pattern}, "Inject a surrogate key into each table that needs it"],
18
+ fk: [%w{primary natural hash}, "Enforce foreign keys using the primary (surrogate), natural keys, or a hash of the natural keys"],
18
19
  }.merge(Compositor.options)
19
20
  end
20
21
 
21
22
  def initialize constellation, name, options = {}, compositor_name = 'Relational'
22
- # Extract recognised options:
23
23
  @option_surrogates = options.delete('surrogates')
24
+
25
+ # Extract recognised options:
26
+ fk = options.delete('fk')
27
+ @option_fk = :primary unless @option_fk != nil # Don't override subclass default
28
+ case fk
29
+ when 'primary', '', nil
30
+ @option_fk = :primary
31
+ when 'natural'
32
+ @option_fk = :natural
33
+ when 'hash'
34
+ @option_fk = :hash
35
+ @option_surrogates = true
36
+ else
37
+ raise "Value #{fk.inspect} for fk option is not supported"
38
+ end
39
+
40
+ @surrogate_name_pattern ||= [true, '', 'true', 'yes', nil].include?(t = @option_surrogates) ? '+ ID' : t
41
+
24
42
  super constellation, name, options, compositor_name
25
43
  end
26
44
 
@@ -46,11 +64,14 @@ module ActiveFacts
46
64
  # If a value type has been mapped to a table, add a column to hold its value
47
65
  inject_value_fields
48
66
 
67
+ # If the compositor calls for audit fields, add them here
68
+ inject_all_audit_fields
69
+
49
70
  # Inject surrogate keys if the options ask for that
50
71
  inject_surrogates if @option_surrogates
51
72
 
52
73
  # Remove the un-used absorption paths
53
- delete_reverse_absorptions
74
+ retract_reverse_mappings
54
75
 
55
76
  # Traverse the absorbed objects to build the path to each required column, including foreign keys:
56
77
  absorb_all_columns
@@ -114,7 +135,7 @@ module ActiveFacts
114
135
  raise "REVISIT: Internal error" unless absorbing_ref.parent_role.object_type == object_type
115
136
  absorbing_ref = absorbing_ref.flip!
116
137
  candidate.full_absorption =
117
- @constellation.FullAbsorption(composition: @composition, absorption: absorbing_ref, object_type: object_type)
138
+ @constellation.FullAbsorption(composition: @composition, mapping: absorbing_ref, object_type: object_type)
118
139
  trace :relational_optimiser, "Fully absorb objectified unary #{object_type.name} into #{f.all_role.single.object_type.name}"
119
140
  candidate.definitely_not_table
120
141
  next object_type
@@ -134,9 +155,9 @@ module ActiveFacts
134
155
  absorption.is_a?(MM::Absorption) && absorption.child_role.base_role == single_pi_role
135
156
  end
136
157
 
137
- absorbing_ref = absorbing_ref.forward_absorption || absorbing_ref.flip!
158
+ absorbing_ref = absorbing_ref.forward_mapping || absorbing_ref.flip!
138
159
  candidate.full_absorption =
139
- @constellation.FullAbsorption(composition: @composition, absorption: absorbing_ref, object_type: object_type)
160
+ @constellation.FullAbsorption(composition: @composition, mapping: absorbing_ref, object_type: object_type)
140
161
  trace :relational_optimiser, "EntityType #{single_pi_role.object_type.name} identifies EntityType #{object_type.name}, so fully absorbs it via #{absorbing_ref.inspect}"
141
162
  candidate.definitely_not_table
142
163
  next object_type
@@ -184,10 +205,10 @@ module ActiveFacts
184
205
  child_candidate = @candidates[a.child_role.object_type]
185
206
 
186
207
  # It's ok if we absorbed them already
187
- next true if a.full_absorption && child_candidate.full_absorption.absorption != a
208
+ next true if a.full_absorption && child_candidate.full_absorption.mapping != a
188
209
 
189
210
  # If our counterpart is a full absorption, don't try to reverse that!
190
- next false if (aa = (a.forward_absorption || a.reverse_absorption)) && aa.full_absorption
211
+ next false if (aa = (a.forward_mapping || a.reverse_mapping)) && aa.full_absorption
191
212
 
192
213
  # Otherwise the other end must already be a table or fully absorbed into one
193
214
  next false unless child_candidate.nil? || child_candidate.is_table || child_candidate.full_absorption
@@ -205,7 +226,7 @@ module ActiveFacts
205
226
  if absorption_paths.size > 0
206
227
  trace :relational_optimiser, "#{object_type.name} is fully absorbed in #{absorption_paths.size} places" do
207
228
  absorption_paths.each do |a|
208
- a = a.flip! if a.forward_absorption
229
+ a = a.flip! if a.forward_mapping
209
230
  trace :relational_optimiser, "#{object_type.name} is fully absorbed via #{a.inspect}"
210
231
  end
211
232
  end
@@ -219,8 +240,8 @@ module ActiveFacts
219
240
  refs_to = candidate.references_to.reject{|a|a.parent_role.base_role.is_identifying}
220
241
  if !refs_to.empty? and non_identifying_refs_from.size == 0
221
242
  refs_to.map! do |a|
222
- a = a.flip! if a.reverse_absorption # We were forward, but the other end must be
223
- a.forward_absorption
243
+ a = a.flip! if a.reverse_mapping # We were forward, but the other end must be
244
+ a.forward_mapping
224
245
  end
225
246
  trace :relational_optimiser, "#{object_type.name} is fully absorbed in #{refs_to.size} places: #{refs_to.map{|ref| ref.inspect}*", "}"
226
247
  candidate.definitely_not_table
@@ -244,12 +265,12 @@ module ActiveFacts
244
265
  end
245
266
 
246
267
  # Remove the unused reverse absorptions:
247
- def delete_reverse_absorptions
268
+ def retract_reverse_mappings
248
269
  @binary_mappings.each do |object_type, mapping|
249
270
  mapping.all_member.to_a. # Avoid problems with deletion from all_member
250
271
  each do |member|
251
- next unless member.is_a?(MM::Absorption)
252
- member.retract if member.forward_absorption # This is the reverse of some absorption
272
+ next unless member.is_a?(MM::Mapping)
273
+ member.retract if member.forward_mapping # This is the reverse of some mapping
253
274
  end
254
275
  mapping.re_rank
255
276
  end
@@ -280,7 +301,7 @@ module ActiveFacts
280
301
  @composition.all_composite.each do |composite|
281
302
  mapping = composite.mapping
282
303
  if mapping.object_type.is_a?(MM::ValueType) and # Composite needs a ValueField
283
- !mapping.all_member.detect{|m| m.is_a?(MM::ValueField)} # And don't already have one
304
+ !mapping.all_member.detect{|m| m.is_a?(MM::ValueField)} # And doesn't already have one
284
305
  trace :relational_columns, "Adding value field for #{mapping.object_type.name}"
285
306
  @constellation.ValueField(
286
307
  :new,
@@ -293,6 +314,13 @@ module ActiveFacts
293
314
  end
294
315
  end
295
316
 
317
+ def inject_all_audit_fields
318
+ end
319
+
320
+ def patterned_name pattern, name
321
+ pattern.sub(/\+/, name)
322
+ end
323
+
296
324
  def inject_surrogates
297
325
  composites = @composition.all_composite.to_a
298
326
  return if composites.empty?
@@ -305,31 +333,18 @@ module ActiveFacts
305
333
  end
306
334
  end
307
335
 
308
- def surrogate_type
309
- @surrogate_type ||= begin
310
- surrogate_type_name = [true, '', 'true', 'yes', nil].include?(t = @option_surrogates) ? 'Auto Counter' : t
311
- # REVISIT: Crappy: choose the first (currently always single)
312
- vocabulary = @composition.all_composite.to_a[0].mapping.object_type.vocabulary
313
- @constellation.ValueType(
314
- vocabulary: vocabulary,
315
- name: surrogate_type_name,
316
- concept: [:new, :implication_rule => "surrogate injection"]
317
- )
318
- end
319
- end
320
-
321
- def inject_surrogate composite, extension = ' ID'
336
+ def inject_surrogate composite, name_pattern = @surrogate_name_pattern
322
337
  trace :surrogates, "Injecting surrogate for #{composite.inspect}" do
323
338
  surrogate_component =
324
339
  @constellation.SurrogateKey(
325
340
  :new,
326
341
  parent: composite.mapping,
327
- name: composite.mapping.name+extension,
328
- object_type: surrogate_type
342
+ name: patterned_name(name_pattern, composite.mapping.name),
343
+ injection_annotation: "surrogate"
329
344
  )
330
345
  index =
331
346
  @constellation.Index(:new, composite: composite, is_unique: true,
332
- presence_constraint: nil, composite_as_primary_index: composite)
347
+ composite_as_primary_index: composite)
333
348
  @constellation.IndexField(access_path: index, ordinal: 0, component: surrogate_component)
334
349
  composite.mapping.re_rank
335
350
  surrogate_component
@@ -337,6 +352,8 @@ module ActiveFacts
337
352
  end
338
353
 
339
354
  def needs_surrogate(composite)
355
+ return true if @option_fk == :hash
356
+
340
357
  object_type = composite.mapping.object_type
341
358
  if MM::ValueType === object_type
342
359
  trace :surrogates, "#{composite.inspect} is a ValueType that #{object_type.is_auto_assigned ? "is auto-assigned already" : "requires a surrogate" }"
@@ -344,7 +361,7 @@ module ActiveFacts
344
361
  end
345
362
 
346
363
  non_key_members, key_members = composite.mapping.all_member.reject do |member|
347
- member.is_a?(MM::Absorption) and member.forward_absorption
364
+ member.is_a?(MM::Absorption) and member.forward_mapping
348
365
  end.partition do |member|
349
366
  member.rank_key[0] > MM::Component::RANK_IDENT
350
367
  end
@@ -470,50 +487,53 @@ module ActiveFacts
470
487
  ordered.each do |member|
471
488
  # Only consider forward Absorptions:
472
489
  next if !member.is_a?(MM::Absorption)
473
- next if member.forward_absorption
490
+ next if member.forward_mapping
474
491
 
475
492
  child_object_type = member.child_role.object_type
476
493
  child_mapping = @binary_mappings[child_object_type]
477
494
  if child_mapping.composite
478
- trace :fks, "FK to #{member.child_role.name} in #{member.inspect_reading}"
495
+ trace :fks, "FK to #{member.child_role.name} in #{member.inspect_reason}"
479
496
  accumulator << child_mapping.composite
480
497
  next
481
498
  end
482
499
 
483
500
  full_absorption = child_object_type.all_full_absorption[@composition]
484
- if full_absorption && full_absorption.absorption.parent_role.fact_type != member.parent_role.fact_type
501
+ if full_absorption &&
502
+ MM::Absorption === full_absorption.mapping &&
503
+ full_absorption.mapping.parent_role.fact_type != member.parent_role.fact_type
485
504
  begin # Follow transitive target absorption
486
- child_object_type = full_absorption.absorption.parent_role.object_type
505
+ child_object_type = full_absorption.mapping.parent_role.object_type
487
506
  end while full_absorption = child_object_type.all_full_absorption[@composition]
488
507
  child_mapping = @binary_mappings[child_object_type]
489
- trace :fks, "FK to #{child_mapping.name} in #{member.inspect_reading} (for fully-absorbed #{member.child_role.name})"
508
+ trace :fks, "FK to #{child_mapping.name} in #{member.inspect_reason} (for fully-absorbed #{member.child_role.name})"
490
509
  accumulator << child_mapping.composite
491
510
  next
492
511
  end
493
512
 
494
- trace :fks, "Descending all of #{member.child_role.name} in #{member.inspect_reading}" do
513
+ trace :fks, "Descending all of #{member.child_role.name} in #{member.inspect_reason}" do
495
514
  enumerate_foreign_keys member, child_mapping, accumulator, path
496
515
  end
497
516
  end
498
517
  accumulator
499
518
  end
500
519
 
501
- # Overwritten by Staging and Datavault subclasses
520
+ # Overwritten by subclasses to modify a structurally-complete schema
502
521
  def apply_schema_transformations
522
+ # replace_exclusive_indicators_by_discriminators
503
523
  end
504
524
 
505
525
  # This member is an Absorption. Process it recursively, absorbing all its members or just a key
506
- # depending on whether the absorbed object is a Composite (or absorbed into one) or not.
526
+ # depending on whether the absorbed object is a Composite (or fully absorbed into one) or not.
507
527
  def absorb_nested mapping, member, paths
508
- # Is this where we absorb a partitioned supertype?
509
- is_partitioned = (ft = member.child_role.fact_type).is_a?(MM::TypeInheritance) && ft.assimilation == 'partitioned'
510
-
528
+ return if MM::ValueField === member
511
529
  # Should we absorb a foreign key or the whole contents?
512
- child_object_type = member.child_role.object_type
530
+ child_object_type = member.object_type
513
531
  child_mapping = @binary_mappings[child_object_type]
514
- if child_mapping.composite && !is_partitioned
515
- trace :relational_columns?, "Absorbing FK to #{member.child_role.name} in #{member.inspect_reading}" do
516
- paths[member] = @constellation.ForeignKey(:new, source_composite: mapping.root, composite: child_mapping.composite, absorption: member)
532
+
533
+ if child_mapping.composite && # The child is a separate table
534
+ !member.is_partitioned_here # We are not absorbing a partitioned supertype
535
+ trace :relational_columns?, "Absorbing FK to #{child_mapping.composite.mapping.name} in #{member.inspect_reason}" do
536
+ paths[member] = @constellation.ForeignKey(:new, source_composite: mapping.root, composite: child_mapping.composite, mapping: member)
517
537
  absorb_key member, child_mapping, paths
518
538
  return
519
539
  end
@@ -523,68 +543,74 @@ module ActiveFacts
523
543
  full_absorption = child_object_type.all_full_absorption[@composition]
524
544
  # We can't use member.full_absorption here, as it's not populated on forked copies
525
545
  # if full_absorption && full_absorption != member.full_absorption
526
- if full_absorption && full_absorption.absorption.parent_role.fact_type != member.parent_role.fact_type
527
-
546
+ if full_absorption &&
547
+ MM::Absorption === full_absorption.mapping &&
548
+ full_absorption.mapping.parent_role.fact_type != member.parent_role.fact_type
528
549
  # REVISIT: This should be done by recursing to absorb_key, not using a loop
529
- absorption = member # Retain this for the ForeignKey
550
+ top_mapping = member # Retain this for the ForeignKey
530
551
  begin # Follow transitive target absorption
531
- member = mirror(full_absorption.absorption, member)
532
- child_object_type = full_absorption.absorption.parent_role.object_type
552
+ member = mirror(full_absorption.mapping, member)
553
+ child_object_type = full_absorption.mapping.parent_role.object_type
533
554
  end while full_absorption = child_object_type.all_full_absorption[@composition]
534
555
  child_mapping = @binary_mappings[child_object_type]
535
556
 
536
- trace :relational_columns?, "Absorbing FK to #{absorption.child_role.name} (fully absorbed into #{child_object_type.name}) in #{member.inspect_reading}" do
537
- paths[absorption] = @constellation.ForeignKey(:new, source_composite: mapping.root, composite: child_mapping.composite, absorption: absorption)
557
+ trace :relational_columns?, "Absorbing FK to #{top_mapping.child_role.name} (fully absorbed into #{child_object_type.name}) in #{member.inspect_reason}" do
558
+ paths[top_mapping] = @constellation.ForeignKey(:new, source_composite: mapping.root, composite: child_mapping.composite, mapping: top_mapping)
538
559
  absorb_key member, child_mapping, paths
539
560
  end
540
561
  return
541
562
  end
542
563
 
543
- # REVISIT: if is_partitioned, don't absorb sibling subtypes!
544
- trace :relational_columns?, "Absorbing all of #{member.child_role.name} in #{member.inspect_reading}" do
564
+ # REVISIT: if member.is_partitioned_here, don't absorb sibling subtypes!
565
+ trace :relational_columns?, "Absorbing all of #{member.name} in #{member.inspect_reason}" do
566
+ if MM::Absorption === member && !member.parent_role.is_mandatory && MM::EntityType === member.object_type
567
+ # REVISIT: If this is an absorbed subtype or full_absorption, add an indicator to that effect
568
+ # Perhaps only if the member contains any non-mandatory leaves?
569
+ trace :relational_columns, "REVISIT: Absorb an indicator for absorbed #{member.inspect}"
570
+ end
545
571
  absorb_all member, child_mapping, paths
546
572
  end
547
573
  end
548
574
 
549
575
  # May be overridden in subclasses
550
- def prefer_natural_key building_natural_key, source_composite, target_composite
551
- false
576
+ def preferred_fk_type building_natural_key, source_composite, target_composite
577
+ @option_fk
552
578
  end
553
579
 
554
580
  # Recursively add members to this component for the existential roles of
555
581
  # the composite mapping for the absorbed (child_role) object:
556
582
  def absorb_key mapping, target, paths
557
583
  building_natural_key = paths.detect{|k,i| i.is_a?(MM::Index) && i.composite_as_natural_index}
558
- prefer_natural = prefer_natural_key(building_natural_key, mapping.root, target.composite)
559
- prefer_natural = false unless !target.composite || target.composite.primary_index != target.composite.natural_index
584
+ fk_type = preferred_fk_type(building_natural_key, mapping.root, target.composite)
560
585
  target.re_rank
561
586
  target.all_member.sort_by(&:ordinal).each do |member|
562
587
  rank = member.rank_key[0]
563
- next unless rank <= MM::Component::RANK_IDENT
564
- if rank == MM::Component::RANK_SURROGATE && prefer_natural
588
+ break unless rank <= MM::Component::RANK_IDENT
589
+ if rank == MM::Component::RANK_SURROGATE && fk_type == :natural
565
590
  next
566
591
  end
592
+ is_primary = member.is_in_primary && mapping.depth == 1
567
593
  member = member.fork_to_new_parent mapping
568
- augment_paths paths, member
569
- if rank == MM::Component::RANK_SURROGATE && !prefer_natural
594
+ augment_paths is_primary, paths, member
595
+ if rank == MM::Component::RANK_SURROGATE && fk_type == :primary
570
596
  break # Will always be first (higher rank), and usurps others
571
- elsif member.is_a?(MM::Absorption)
572
- object_type = member.child_role.object_type
573
- full_absorption = @composition.all_full_absorption[member.child_role.object_type]
597
+ elsif member.is_a?(MM::Mapping) && !member.is_a?(MM::ValueField)
598
+ object_type = member.object_type
599
+ full_absorption = @composition.all_full_absorption[object_type]
574
600
  if full_absorption
575
601
  # The target object is fully absorbed. Absorb a key to where it was absorbed
576
602
  # We can't recurse here, because we must descend supertype absorptions
577
- while full_absorption
578
- trace :relational_columns?, "Absorbing key of fully absorbed #{member.child_role.name}" do
579
- member = mirror full_absorption.absorption, member
580
- augment_paths paths, member
603
+ while full_absorption && MM::Absorption === full_absorption.mapping
604
+ trace :relational_columns?, "Absorbing key of fully absorbed #{object_type.name}" do
605
+ member = mirror(cloned = full_absorption.mapping, member)
606
+ augment_paths cloned.is_in_primary, paths, member
581
607
  # Descend so the key fields get fully populated
582
- absorb_key member, full_absorption.absorption.parent, paths
583
- full_absorption = @composition.all_full_absorption[member.child_role.object_type]
608
+ absorb_key member, full_absorption.mapping.parent, paths
609
+ full_absorption = @composition.all_full_absorption[member.object_type]
584
610
  end
585
611
  end
586
612
  else
587
- absorb_key member, @binary_mappings[member.child_role.object_type], paths
613
+ absorb_key member, @binary_mappings[object_type], paths
588
614
  end
589
615
  end
590
616
  end
@@ -598,11 +624,12 @@ module ActiveFacts
598
624
 
599
625
  pcs = []
600
626
  newpaths = {}
601
- if mapping.composite || mapping.full_absorption || mapping.parent_role.fact_type.is_a?(MM::TypeInheritance)
627
+ if mapping.composite || mapping.full_absorption || mapping.is_type_inheritance
602
628
  pcs = find_uniqueness_constraints(mapping)
603
629
 
604
630
  # Don't build an index from the same PresenceConstraint twice on the same composite (e.g. for a subtype)
605
631
  existing_pcs = mapping.root.all_access_path.select{|ap| MM::Index === ap}.map(&:presence_constraint)
632
+ # REVISIT: Some of paths.keys are not PresenceConstraints here!
606
633
  newpaths = make_new_paths mapping, paths.keys+existing_pcs, pcs
607
634
  end
608
635
 
@@ -610,14 +637,15 @@ module ActiveFacts
610
637
  ordered = from.all_member.sort_by(&:ordinal)
611
638
  ordered.each do |member|
612
639
  trace :relational_columns, proc {"#{top_level ? 'Existing' : 'Absorbing'} #{member.inspect}"} do
640
+ is_primary = member.is_in_primary
613
641
  unless top_level # Top-level members are already instantiated
614
642
  member = member.fork_to_new_parent mapping
615
643
  end
616
644
  rel = paths.merge(relevant_paths(newpaths, member))
617
- augment_paths rel, member
645
+ augment_paths is_primary, rel, member
618
646
 
619
- if member.is_a?(MM::Absorption) && !member.forward_absorption
620
- # Only forward absorptions here please...
647
+ if member.is_a?(MM::Mapping) && !member.forward_mapping
648
+ # Process forward absorptions recursively
621
649
  absorb_nested mapping, member, rel
622
650
  end
623
651
  end
@@ -670,7 +698,8 @@ module ActiveFacts
670
698
  pc.role_sequence.all_role_ref.size == 1 and
671
699
  mapping.is_a?(MM::Absorption) and
672
700
  fa = mapping.full_absorption and
673
- pc.role_sequence.all_role_ref.single.role.base_role == fa.absorption.parent_role.base_role
701
+ fa.mapping.is_a?(MM::Absorption) and
702
+ pc.role_sequence.all_role_ref.single.role.base_role == fa.mapping.parent_role.base_role
674
703
  )
675
704
  end # Alethic uniqueness constraint on far end
676
705
 
@@ -691,7 +720,7 @@ module ActiveFacts
691
720
  end
692
721
  pcs = non_absorption_pcs
693
722
 
694
- trace :relational_paths, "Uniqueness Constraints for #{mapping.object_type.name}" do
723
+ trace :relational_paths, "Uniqueness Constraints for #{mapping.name}" do
695
724
  pcs.each do |pc|
696
725
  trace :relational_paths, "#{pc.describe.inspect}#{pc.is_preferred_identifier ? ' (PI)' : ''}"
697
726
  end
@@ -706,7 +735,8 @@ module ActiveFacts
706
735
  trace :relational_paths?, "Adding #{new_pcs.size} new indices for presence constraints on #{mapping.inspect}" do
707
736
  new_pcs.each do |pc|
708
737
  newpaths[pc] = index = @constellation.Index(:new, composite: mapping.root, is_unique: true, presence_constraint: pc)
709
- if mapping.root.mapping.object_type.preferred_identifier == pc and
738
+ identified_object = mapping.is_partitioned_here ? mapping.child_role.object_type : mapping.root.mapping.object_type
739
+ if identified_object.preferred_identifier == pc and
710
740
  !@composition.all_full_absorption[mapping.object_type] and # REVISIT: This clause might now be unnecessary
711
741
  !mapping.root.natural_index
712
742
  mapping.root.natural_index = index
@@ -736,14 +766,14 @@ module ActiveFacts
736
766
  rel
737
767
  end
738
768
 
739
- def augment_paths paths, mapping
740
- return unless MM::Indicator === mapping || MM::ValueType === mapping.object_type
769
+ def augment_paths is_primary, paths, mapping
770
+ return unless !(MM::Mapping === mapping) || MM::ValueType === mapping.object_type
741
771
 
742
772
  if MM::ValueField === mapping && mapping.parent.composite # ValueType that's a composite (table) by itself
743
773
  # This AccessPath has exactly one field and no presence constraint, so just make the index.
744
774
  composite = mapping.parent.composite
745
775
  paths[nil] =
746
- index = @constellation.Index(:new, composite: mapping.root, is_unique: true, presence_constraint: nil, composite_as_natural_index: composite)
776
+ index = @constellation.Index(:new, composite: mapping.root, is_unique: true, composite_as_natural_index: composite)
747
777
  composite.primary_index ||= index
748
778
  end
749
779
 
@@ -751,8 +781,13 @@ module ActiveFacts
751
781
  trace :relational_paths, "Adding #{mapping.inspect} to #{path.inspect}" do
752
782
  case path
753
783
  when MM::Index
784
+ # If we're using hash surrogates, refuse to include a surrogate in the natural index:
785
+ next if @option_fk == :hash && (!!is_primary == (mapping.root.natural_index == path))
754
786
  @constellation.IndexField(access_path: path, ordinal: path.all_index_field.size, component: mapping)
755
787
  when MM::ForeignKey
788
+ # If we're using hash surrogates, foreign keys only contain surrogates.
789
+ # REVISIT: What if not all FK target tables have a surrogate? Answer: they do because they must.
790
+ next if @option_fk == :hash && !is_primary
756
791
  @constellation.ForeignKeyField(foreign_key: path, ordinal: path.all_foreign_key_field.size, component: mapping)
757
792
  end
758
793
  end
@@ -766,14 +801,14 @@ module ActiveFacts
766
801
  next if MM::Index === path
767
802
 
768
803
  next if path.all_foreign_key_field.size == path.all_index_field.size
769
- target_object_type = path.absorption.child_role.object_type
804
+ target_object_type = path.mapping.object_type
770
805
  while fa = target_object_type.all_full_absorption[@composition]
771
- target_object_type = fa.absorption.parent_role.object_type
806
+ target_object_type = fa.mapping.parent.object_type
772
807
  end
773
808
  target = @composites[target_object_type]
774
- prefer_natural = prefer_natural_key(false, composite, target)
809
+ fk_type = preferred_fk_type(false, composite, target)
775
810
  trace :relational_paths, "Completing #{path.inspect} to #{target.mapping.inspect}"
776
- index = (prefer_natural && target.natural_index) || target.primary_index
811
+ index = (fk_type == :natural && target.natural_index) || target.primary_index
777
812
  if index
778
813
  index.all_index_field.each do |index_field|
779
814
  @constellation.IndexField access_path: path, ordinal: index_field.ordinal, component: index_field.component
@@ -816,13 +851,21 @@ module ActiveFacts
816
851
 
817
852
  # References from us are things we can own (non-Mappings) or have a unique forward absorption for
818
853
  def references_from
819
- @mapping.all_member.select{|m| !m.is_a?(MM::Absorption) or !m.forward_absorption && m.parent_role.is_unique }
854
+ @mapping.all_member.select do |m|
855
+ !m.is_a?(MM::Absorption) or
856
+ !m.forward_mapping && m.parent_role.is_unique # A forward absorption has no forward absorption
857
+ end
820
858
  end
821
859
  alias_method :rf, :references_from
822
860
 
823
861
  # References to us are reverse absorptions where the forward absorption can absorb us
824
862
  def references_to
825
- @mapping.all_member.select{|m| m.is_a?(MM::Absorption) and f = m.forward_absorption and f.parent_role.is_unique}
863
+ # REVISIT: If some other object has a Mapping to us, that should be in this list
864
+ @mapping.all_member.select do |m|
865
+ m.is_a?(MM::Absorption) and
866
+ f = m.forward_mapping and # This Absorption has a forward counterpart, so must be reverse
867
+ f.parent_role.is_unique
868
+ end
826
869
  end
827
870
  alias_method :rt, :references_to
828
871
 
@@ -892,10 +935,10 @@ module ActiveFacts
892
935
 
893
936
  absorbing_ref = mapping.all_member.detect{|m| m.is_a?(MM::Absorption) && m.child_role.fact_type == fact_type}
894
937
 
895
- absorbing_ref = absorbing_ref.flip! if absorbing_ref.reverse_absorption # We were forward, but the other end must be
896
- absorbing_ref = absorbing_ref.forward_absorption
938
+ absorbing_ref = absorbing_ref.flip! if absorbing_ref.reverse_mapping # We were forward, but the other end must be
939
+ absorbing_ref = absorbing_ref.forward_mapping
897
940
  self.full_absorption =
898
- o.constellation.FullAbsorption(composition: composition, absorption: absorbing_ref, object_type: o)
941
+ o.constellation.FullAbsorption(composition: composition, mapping: absorbing_ref, object_type: o)
899
942
  trace :relational_defaults, "Supertype #{fact_type.supertype_role.name} fully absorbs subtype #{o.name} via #{absorbing_ref.inspect}"
900
943
  definitely_not_table
901
944
  return