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
@@ -1,798 +0,0 @@
1
- #
2
- # ActiveFacts Compositions, DocGraph Compositor.
3
- #
4
- # Computes an Document/Semantic Graph schema.
5
- #
6
- # Copyright (c) 2017 Factil Pty Ltd. Read the LICENSE file.
7
- #
8
- require "activefacts/compositions"
9
-
10
- module ActiveFacts
11
- module Metamodel
12
- class Composite
13
- def is_document
14
- @isa_document = true
15
- end
16
-
17
- def is_document?
18
- @isa_document
19
- end
20
-
21
- def is_triple
22
- @isa_triple = true
23
- end
24
-
25
- def is_triple?
26
- @isa_triple
27
- end
28
- end
29
- end
30
- end
31
-
32
- module ActiveFacts
33
- module Compositions
34
- class DocGraph < Compositor
35
- MM = ActiveFacts::Metamodel unless const_defined?(:MM)
36
- TRIPLE_ANNOTATION = /triple/
37
-
38
- def self.options
39
- {
40
- nested: ['Boolean', "Compose nested documents"],
41
- }.merge(Compositor.options)
42
- end
43
-
44
- def initialize constellation, name, options = {}
45
- # Extract recognised options:
46
- @option_nested = options.delete('nested')
47
- super constellation, name, options, 'DocGraph'
48
- end
49
-
50
- def generate
51
- super
52
-
53
- trace :docgraph_details!, "Generating docgraph composition" do
54
- # Make a data structure to help in computing the documents
55
- make_candidates
56
-
57
- # Apply any obvious document/graph factors
58
- assign_default_docgraph
59
-
60
- # Figure out how best to absorb things to reduce the number of documents
61
- optimise_absorption
62
-
63
- # Actually make a Composite object for each document and triple:
64
- make_composites
65
-
66
- # If a value type has been mapped to a document, add a property to hold its value
67
- inject_value_fields
68
-
69
- # # Inject surrogate keys if the options ask for that
70
- # inject_surrogates if @option_surrogates
71
-
72
- # # Remove the un-used absorption paths
73
- # delete_reverse_absorptions
74
-
75
- # Traverse the absorbed objects to build the path to each required property, including foreign keys:
76
- absorb_all_properties
77
-
78
- # Remove mappings for objects we have absorbed
79
- clean_unused_mappings
80
- end
81
-
82
- trace :docgraph!, "Full #{self.class.basename} composition" do
83
- @document_composites.values.sort_by{|composite| composite.mapping.name}.each do |composite|
84
- composite.show_trace
85
- end
86
- @triple_composites.values.sort_by{|composite| composite.mapping.name}.each do |composite|
87
- composite.show_trace
88
- end
89
- end
90
- end
91
-
92
- def make_candidates
93
- @candidates = @binary_mappings.inject({}) do |hash, (absorption, mapping)|
94
- hash[mapping.object_type] = Candidate.new(self, mapping)
95
- hash
96
- end
97
- end
98
-
99
- def assign_default_docgraph
100
- trace :docgraph_defaults!, "Preparing DocGraph composition by setting default assumptions" do
101
- @candidates.each do |object_type, candidate|
102
- candidate.assign_default(@composition)
103
- end
104
- end
105
- end
106
-
107
- def optimise_absorption
108
- trace :docgraph_optimiser!, "Optimise DocGraph Composition" do
109
- undecided = @candidates.keys.select{|object_type| @candidates[object_type].is_tentative}
110
- pass = 0
111
- finalised = []
112
- begin
113
- pass += 1
114
- trace :docgraph_optimiser, "Starting optimisation pass #{pass}" do
115
- finalised = optimise_absorption_pass(undecided)
116
- end
117
- trace :docgraph_optimiser, "Finalised #{finalised.size} on this pass: #{finalised.map{|f| f.name}*', '}"
118
- undecided -= finalised
119
- end while !finalised.empty?
120
- end
121
- end
122
-
123
- def optimise_absorption_pass undecided
124
- undecided.select do |object_type|
125
- candidate = @candidates[object_type]
126
- trace :docgraph_optimiser, "Considering possible status of #{object_type.name}" do
127
-
128
- # Rule 1: Always absorb an objectified unary into its role player (unless its forced to be separate)
129
- if !object_type.is_separate && (f = object_type.fact_type) && f.all_role.size == 1
130
- absorbing_ref = candidate.mapping.all_member.detect{|a| a.is_a?(MM::Absorption) and a.child_role.base_role == f.all_role.single}
131
- raise "REVISIT: Internal error" unless absorbing_ref.parent_role.object_type == object_type
132
- absorbing_ref = absorbing_ref.flip!
133
- candidate.full_absorption =
134
- @constellation.FullAbsorption(composition: @composition, absorption: absorbing_ref, object_type: object_type)
135
- trace :docgraph_optimiser, "Fully absorb objectified unary #{object_type.name} into #{f.all_role.single.object_type.name}"
136
- candidate.definitely_not_document
137
- next object_type
138
- end
139
-
140
- # # Rule 2: If the preferred_identifier contains one role only, played by an entity type that can absorb us, do that:
141
- # # (Leave pi_roles intact for further use below)
142
- # absorbing_ref = nil
143
- pi_roles = []
144
- # if object_type.is_a?(MM::EntityType) and # We're an entity type
145
- # pi_roles = object_type.preferred_identifier_roles and # Our PI
146
- # pi_roles.size == 1 and # has one role
147
- # single_pi_role = pi_roles[0] and # that role is
148
- # single_pi_role.object_type.is_a?(MM::EntityType) and # played by another Entity Type
149
- # absorbing_ref =
150
- # candidate.mapping.all_member.detect do |absorption|
151
- # absorption.is_a?(MM::Absorption) && absorption.child_role.base_role == single_pi_role
152
- # end
153
- #
154
- # absorbing_ref = absorbing_ref.forward_absorption || absorbing_ref.flip!
155
- # candidate.full_absorption =
156
- # @constellation.FullAbsorption(composition: @composition, absorption: absorbing_ref, object_type: object_type)
157
- # trace :docgraph_optimiser, "EntityType #{single_pi_role.object_type.name} identifies EntityType #{object_type.name}, so fully absorb it via #{absorbing_ref.inspect}"
158
- # candidate.definitely_not_document
159
- # next object_type
160
- # end
161
-
162
- # Rule 3: If there's more than one absorption path and any functional dependencies that can't absorb us, it's a document
163
- trace :docgraph_optimiser, "From-references for #{object_type.name}(#{pi_roles.map(&:object_type).map(&:name)*', '}) are #{candidate.references_from.map(&:inspect)*', '}"
164
- non_identifying_refs_from =
165
- candidate.references_from.reject do |member|
166
- case member
167
- when MM::Absorption
168
- pi_roles.include?(member.child_role.base_role)
169
- when MM::Indicator
170
- pi_roles.include?(member.role)
171
- else
172
- false
173
- end
174
- end
175
- trace :docgraph_optimiser, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles" do
176
- non_identifying_refs_from.each do |a|
177
- trace :docgraph_optimiser, a.inspect
178
- end
179
- end
180
-
181
- trace :docgraph_optimiser, "#{object_type.name} has #{candidate.references_to.size} references to it" do
182
- candidate.references_to.each do |a|
183
- trace :docgraph_optimiser, a.inspect
184
- end
185
- end
186
-
187
- # Both of these conditions are not relevant for documents
188
- # if candidate.references_to.size > 1 and # More than one place wants us
189
- # non_identifying_refs_from.size > 0 # And we carry dependent values so cannot be absorbed
190
- # trace :docgraph_optimiser, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional dependencies and #{candidate.references_to.size} absorption paths so it is a document"
191
- # candidate.definitely_document
192
- # next object_type
193
- # end
194
-
195
- # At this point, this object either has no functional dependencies or only one place it would be absorbed
196
- next false if !candidate.is_document # We can't reduce the number of tables by absorbing this one
197
-
198
- absorption_paths =
199
- ( non_identifying_refs_from + # But we should exclude any that are already involved in an absorption; pre-decided ET=>ET or supertype absorption!
200
- candidate.references_to # These are our reverse absorptions that could absorb us
201
- ).select do |a|
202
- next false unless a.is_a?(MM::Absorption) # Skip Indicators, we can't be absorbed there
203
- child_candidate = @candidates[a.child_role.object_type]
204
-
205
- # It's ok if we absorbed them already
206
- next true if a.full_absorption && child_candidate.full_absorption.absorption != a
207
-
208
- # If our counterpart is a full absorption, don't try to reverse that!
209
- next false if (aa = (a.forward_absorption || a.reverse_absorption)) && aa.full_absorption
210
-
211
- # Otherwise the other end must already be a document or fully absorbed into one
212
- next false unless child_candidate.nil? || child_candidate.is_document || child_candidate.full_absorption
213
-
214
- next false unless a.child_role.is_unique && a.parent_role.is_unique # Must be one-to-one
215
-
216
- # next true if pi_roles.size == 1 && pi_roles.include?(a.parent_role) # Allow the sole identifying role for this object
217
- next false unless a.parent_role.is_mandatory # Don't absorb an object along a non-mandatory role
218
- true
219
- end
220
-
221
- trace :docgraph_optimiser, "#{object_type.name} has #{absorption_paths.size} absorption paths"
222
-
223
- # # Rule 4: If this object can be fully absorbed along non-identifying roles, do that (maybe flip some absorptions)
224
- # if absorption_paths.size > 0
225
- # trace :docgraph_optimiser, "#{object_type.name} is fully absorbed in #{absorption_paths.size} places" do
226
- # absorption_paths.each do |a|
227
- # a = a.flip! if a.forward_absorption
228
- # trace :docgraph_optimiser, "#{object_type.name} is fully absorbed via #{a.inspect}"
229
- # end
230
- # end
231
- #
232
- # candidate.definitely_not_document
233
- # next object_type
234
- # end
235
-
236
- # Rule 5: If this object has no functional dependencies (only its identifier), it can be absorbed in multiple places
237
- # We don't create FullAbsorptions, because they're only used to resolve references to this object; and there are none here
238
- refs_to = candidate.references_to.reject{|a|a.parent_role.base_role.is_identifying}
239
- if !refs_to.empty? and non_identifying_refs_from.size == 0
240
- refs_to.map! do |a|
241
- a = a.flip! if a.reverse_absorption # We were forward, but the other end must be
242
- a.forward_absorption
243
- end
244
- trace :docgraph_optimiser, "#{object_type.name} is fully absorbed in #{refs_to.size} places: #{refs_to.map{|ref| ref.inspect}*", "}"
245
- candidate.definitely_not_document
246
- next object_type
247
- end
248
-
249
- false # Otherwise we failed to make a decision about this object type
250
- end
251
- end
252
- end
253
-
254
- def top component
255
- component.parent ? top(component.parent) : component
256
- end
257
-
258
- # Remove the unused reverse absorptions:
259
- def delete_reverse_absorptions
260
- # @binary_mappings.each do |object_type, mapping|
261
- # mapping.all_member.to_a. # Avoid problems with deletion from all_member
262
- # each do |member|
263
- # next unless member.is_a?(MM::Absorption)
264
- # next unless member.forward_absorption
265
- # # retract if this absorption is not in a document or the forward absorption is in a document
266
- # member.retract if @document_composites[top(member.forward_absorption).object_type] || !@document_composites[top(member).object_type]
267
- # end
268
- # mapping.re_rank
269
- # end
270
- end
271
-
272
- # After all document/triple decisions are made, convert Mappings for triples into Composites and retract the rest:
273
- def make_composites
274
- @document_composites = {}
275
- @triple_composites = {}
276
- @candidates.keys.to_a.each do |object_type|
277
- candidate = @candidates[object_type]
278
-
279
- if (candidate.is_document or candidate.is_triple) and !candidate.is_tentative
280
- make_composite(candidate)
281
- else
282
- @candidates.delete(object_type)
283
- end
284
- end
285
- end
286
-
287
- def make_composite candidate
288
- mapping = candidate.mapping
289
- composite = @constellation.Composite(mapping, composition: @composition)
290
- if candidate.is_document
291
- @document_composites[mapping.object_type] = composite
292
- composite.is_document
293
- else
294
- @triple_composites[mapping.object_type] = composite
295
- composite.is_triple
296
- end
297
- end
298
-
299
- # Inject a ValueField for each value type that is a document:
300
- def inject_value_fields
301
- @document_composites.each do |key, composite|
302
- mapping = composite.mapping
303
- if mapping.object_type.is_a?(MM::ValueType) and # Composite needs a ValueField
304
- !mapping.all_member.detect{|m| m.is_a?(MM::ValueField)} # And don't already have one
305
- trace :docgraph_properties, "Adding value field for #{mapping.object_type.name}"
306
- @constellation.ValueField(
307
- :new,
308
- parent: mapping,
309
- name: mapping.object_type.name+" Value",
310
- object_type: mapping.object_type
311
- )
312
- mapping.re_rank
313
- end
314
- end
315
- end
316
-
317
- def clean_unused_mappings
318
- @candidates.keys.to_a.each do |object_type|
319
- candidate = @candidates[object_type]
320
- next if candidate.is_document or candidate.is_triple
321
- mapping = candidate.mapping
322
- mapping.retract
323
- @binary_mappings.delete(object_type)
324
- end
325
- end
326
-
327
- def is_empty_inheritance mapping
328
- # Cannot be an empty inheritance unless it's an TypeInheritance absorption
329
- return false if !mapping.is_a?(MM::Absorption) || !mapping.parent_role.fact_type.is_a?(MM::TypeInheritance)
330
-
331
- # It's empty if it's a TypeInheritance which has no non-empty members
332
- !mapping.all_member.to_a.any? do |member|
333
- !is_empty_inheritance(member)
334
- end
335
- end
336
-
337
- def elide_empty_inheritance mapping
338
- mapping.all_member.to_a.each do |member|
339
- if member.is_a?(MM::Absorption) && member.parent_role.fact_type.is_a?(MM::TypeInheritance)
340
- elide_empty_inheritance(member)
341
- if member.all_member.size == 0
342
- trace :docgraph, "Retracting empty inheritance #{member.inspect}"
343
- member.retract
344
- end
345
- end
346
- end
347
- end
348
-
349
- # Absorb all items which aren't documents (and keys to those which are) recursively
350
- def absorb_all_properties
351
- trace :docgraph_properties!, "Computing contents of all documents and triples" do
352
- @document_composites.values.sort_by{|composite| composite.mapping.name}.each do |composite|
353
- trace :docgraph_properties, "Computing contents of #{composite.mapping.name}" do
354
- absorb_all(composite.mapping, composite.mapping)
355
- end
356
- end
357
- end
358
- end
359
-
360
- #
361
- # Rename parents functions defined because they are used in both Staging and Datavault subclasses
362
- #
363
- def apply_name pattern, name
364
- pattern.sub(/\+/, name)
365
- end
366
-
367
- def rename_parents
368
- @document_composites.each do |key, composite|
369
- composite.mapping.name = apply_name(@option_stg_name, composite.mapping.name)
370
- end
371
- end
372
-
373
- # This member is an Absorption. Process it recursively, either absorbing all its members if it is not a document,
374
- # deleting it if it is a semantic triple or just keeping the key if it is not a semantic triple
375
- def absorb_subdoc mapping, member, paths, stack
376
- trace :docgraph_properties, "Absorb subdoc of #{member.inspect} into #{mapping.name}" do
377
- # In the DocGraph composition, either absorb the contents or devolve a triple
378
- trace :docgraph_properties, "(parent #{member.parent_role.object_type.name}, child #{member.child_role.object_type.name})"
379
- child_object_type = member.child_role.object_type
380
- child_mapping = @binary_mappings[child_object_type]
381
- if @triple_composites[child_object_type]
382
- trace :docgraph_triple, "Eliminate #{child_object_type.name} subdoc"
383
- member.retract
384
- mapping.re_rank
385
- return
386
- end
387
-
388
- # Is our target object_type fully absorbed (and not through this absorption)?
389
- full_absorption = child_object_type.all_full_absorption[@composition]
390
- # We can't use member.full_absorption here, as it's not populated on forked copies
391
- # if full_absorption && full_absorption != member.full_absorption
392
- if full_absorption && full_absorption.absorption.parent_role.fact_type != member.parent_role.fact_type
393
-
394
- # REVISIT: This should be done by recursing to absorb_key, not using a loop
395
- absorption = member # Retain this for the ForeignKey
396
- begin # Follow transitive target absorption
397
- member = mirror(full_absorption.absorption, member)
398
- child_object_type = full_absorption.absorption.parent_role.object_type
399
- end while full_absorption = child_object_type.all_full_absorption[@composition]
400
- child_mapping = @binary_mappings[child_object_type]
401
-
402
- trace :docgraph_properties, "Absorbing all of #{member.child_role.name} in #{member.inspect_reading}"
403
- absorb_all(member, child_mapping, paths, stack)
404
- return
405
- end
406
-
407
- absorb_all(member, child_mapping, paths, stack)
408
- end
409
- end
410
-
411
- # Handle the reverse absorptions of the mapping
412
- def absorb_nested mapping, member, paths, stack
413
- trace :docgraph_properties, "Absorb nested of #{member.inspect} into #{mapping.name}" do
414
- # In the DocGraph composition, either absorb the contents or devolve a triple
415
- if @triple_composites[member.child_role.object_type]
416
- trace :docgraph_triple, "Handle #{member.inspect} as a semantic triple"
417
- member.retract
418
- mapping.re_rank
419
- return
420
- end
421
-
422
- # This is a nested structure, annotate as Nested, flip the member and absorb all
423
- @constellation.Nesting(member, 0, index_role: member.child_role)
424
-
425
- # member.flip!
426
- child_object_type = member.child_role.object_type
427
- child_mapping = @binary_mappings[child_object_type]
428
- absorb_all(member, child_mapping, paths, stack)
429
- end
430
- end
431
-
432
- # May be overridden in subclasses
433
- def prefer_natural_key building_natural_key, source_composite, target_composite
434
- false
435
- end
436
-
437
- # Augment the mapping with copies of the children of the "from" mapping.
438
- # At the top level, no "from" is given and the children already exist
439
- def absorb_all mapping, from, paths = {}, stack = []
440
- trace :docgraph_properties, "Absorbing all from #{from.inspect} into #{mapping.name}" do
441
- top_level = mapping == from
442
-
443
- pcs = []
444
- newpaths = {}
445
- if mapping.composite || mapping.full_absorption
446
- pcs = find_uniqueness_constraints(mapping)
447
-
448
- # Don't build an index from the same PresenceConstraint twice on the same composite (e.g. for a subtype)
449
- existing_pcs = mapping.root.all_access_path.select{|ap| MM::Index === ap}.map(&:presence_constraint)
450
- newpaths = make_new_paths(mapping, paths.keys+existing_pcs, pcs)
451
- end
452
-
453
- from.re_rank
454
- substack = stack + [from.object_type]
455
- ordered = from.all_member.sort_by(&:ordinal)
456
- ordered.each do |member|
457
- trace :docgraph_properties, "... considering #{member.child_role.object_type.name}"
458
-
459
- # Only proceed if there is no absorption loop and we are not jumping to another document
460
- if !absorption_loop(member, substack) && !@document_composites[member.child_role.object_type]
461
- unless top_level # Top-level members are already instantiated
462
- member = member.fork_to_new_parent(mapping)
463
- end
464
- rel = paths.merge(relevant_paths(newpaths, member))
465
- augment_paths(rel, member)
466
-
467
- if member.is_a?(MM::Absorption) && !member.forward_absorption && member.parent_role.is_unique && member.child_role.object_type.is_a?(MM::EntityType)
468
- # Only forward absorptions here please...
469
- absorb_subdoc(mapping, member, rel, substack)
470
- elsif member.is_a?(MM::Absorption) && (member.forward_absorption || !member.parent_role.is_unique) # && top_level
471
- absorb_nested(mapping, member, rel, substack)
472
- end
473
- end
474
- end
475
-
476
- # Clean up if mapping does not have any members
477
- if mapping.all_member.size == 0
478
- mapping_parent = mapping.parent
479
- mapping.retract
480
- mapping_parent.re_rank
481
- end
482
-
483
- newpaths.values.select{|ix| ix.all_index_field.size == 0}.each(&:retract)
484
- end
485
- end
486
-
487
- def absorption_loop(absorption, stack)
488
- trace :docgraph_properties, "Stack is #{stack.map{|ot| ot.name} * ', '}"
489
- result = stack.any? {|ot| ot == absorption.child_role.object_type}
490
- trace :docgraph_properties, "absorption child is #{absorption.child_role.object_type.name}, loop is #{result}"
491
- result
492
- end
493
-
494
- # Find all PresenceConstraints to index the object in this Mapping
495
- def find_uniqueness_constraints mapping
496
- return [] unless mapping.object_type.is_a?(MM::EntityType)
497
-
498
- start_roles =
499
- mapping.
500
- object_type.
501
- all_role_transitive. # Includes objectification roles for objectified fact types
502
- select do |role|
503
- (role.is_unique || # Must be unique on near role
504
- role.fact_type.is_unary) && # Or be a unary role
505
- !(role.fact_type.is_a?(MM::TypeInheritance) && role == role.fact_type.supertype_role) # allow roles as subtype
506
- end.
507
- map(&:counterpart). # (Same role if it's a unary)
508
- compact. # Ignore nil counterpart of a role in an n-ary
509
- map(&:base_role). # In case it's a link fact type
510
- uniq
511
-
512
- pcs =
513
- start_roles.
514
- flat_map(&:all_role_ref). # All role_refs
515
- map(&:role_sequence). # The role_sequence
516
- uniq.
517
- flat_map(&:all_presence_constraint).
518
- uniq.
519
- reject do |pc|
520
- pc.max_frequency != 1 || # Must be unique
521
- pc.enforcement || # and alethic
522
- pc.role_sequence.all_role_ref.detect do |rr|
523
- !start_roles.include?(rr.role) # and span only valid roles
524
- end || # and not be the full absorption path
525
- ( # Reject a constraint that caused full absorption
526
- pc.role_sequence.all_role_ref.size == 1 and
527
- mapping.is_a?(MM::Absorption) and
528
- fa = mapping.full_absorption and
529
- pc.role_sequence.all_role_ref.single.role.base_role == fa.absorption.parent_role.base_role
530
- )
531
- end # Alethic uniqueness constraint on far end
532
-
533
- non_absorption_pcs = pcs.reject do |pc|
534
- # An absorption PC is a PC that covers some role that is involved in a FullAbsorption
535
- full_absorptions =
536
- pc.
537
- role_sequence.
538
- all_role_ref.
539
- map(&:role).
540
- flat_map do |role|
541
- (role.all_absorption_as_parent_role.to_a + role.all_absorption_as_child_role.to_a).
542
- select do |abs|
543
- abs.full_absorption && abs.full_absorption.composition == @composition
544
- end
545
- end
546
- full_absorptions.size > 0
547
- end
548
- pcs = non_absorption_pcs
549
-
550
- trace :docgraph_paths, "Uniqueness Constraints for #{mapping.object_type.name}" do
551
- pcs.each do |pc|
552
- trace :docgraph_paths, "#{pc.describe.inspect}#{pc.is_preferred_identifier ? ' (PI)' : ''}"
553
- end
554
- end
555
-
556
- pcs
557
- end
558
-
559
- def make_new_paths mapping, existing_pcs, pcs
560
- newpaths = {}
561
- new_pcs = pcs-existing_pcs
562
- trace :docgraph_paths, "Adding #{new_pcs.size} new indices for presence constraints on #{mapping.inspect}" do
563
- new_pcs.each do |pc|
564
- newpaths[pc] = index = @constellation.Index(:new, composite: mapping.root, is_unique: true, presence_constraint: pc)
565
- if mapping.object_type.preferred_identifier == pc and
566
- !@composition.all_full_absorption[mapping.object_type] and
567
- !mapping.root.natural_index
568
- mapping.root.natural_index = index
569
- mapping.root.primary_index ||= index # Not if we have a surrogate already
570
- end
571
- trace :docgraph_paths, "Added new index #{index.inspect} for #{pc.describe} on #{pc.role_sequence.all_role_ref.map(&:role).map(&:fact_type).map(&:default_reading).inspect}"
572
- end
573
- end
574
- newpaths
575
- end
576
-
577
- def relevant_paths path_hash, component
578
- rel = {} # REVISIT: return a hash subset of path_hash containing paths relevant to this component
579
- case component
580
- when MM::Absorption
581
- role = component.child_role.base_role
582
- when MM::Indicator
583
- role = component.role
584
- else
585
- return rel # Can't participate in an AccessPath
586
- end
587
-
588
- path_hash.each do |pc, path|
589
- next unless pc.role_sequence.all_role_ref.detect{|rr| rr.role == role}
590
- rel[pc] = path
591
- end
592
- rel
593
- end
594
-
595
- def augment_paths paths, mapping
596
- return unless MM::Indicator === mapping || MM::ValueType === mapping.object_type
597
-
598
- if MM::ValueField === mapping && mapping.parent.composite # ValueType that's a composite (table) by itself
599
- # This AccessPath has exactly one field and no presence constraint, so just make the index.
600
- composite = mapping.parent.composite
601
- paths[nil] =
602
- index = @constellation.Index(:new, composite: mapping.root, is_unique: true, presence_constraint: nil, composite_as_natural_index: composite)
603
- composite.primary_index ||= index
604
- end
605
-
606
- paths.each do |pc, path|
607
- trace :docgraph_paths, "Adding access path #{mapping.inspect} to #{path.inspect}" do
608
- case path
609
- when MM::Index
610
- @constellation.IndexField(access_path: path, ordinal: path.all_index_field.size, component: mapping)
611
- when MM::ForeignKey
612
- @constellation.ForeignKeyField(foreign_key: path, ordinal: path.all_foreign_key_field.size, component: mapping)
613
- end
614
- end
615
- end
616
- end
617
-
618
- # Make a new Absorption in the reverse direction from the one given
619
- def mirror absorption, parent
620
- @constellation.fork(
621
- absorption,
622
- guid: :new,
623
- object_type: absorption.parent_role.object_type,
624
- parent: parent,
625
- parent_role: absorption.child_role,
626
- child_role: absorption.parent_role,
627
- ordinal: 0,
628
- name: role_name(absorption.parent_role)
629
- )
630
- end
631
-
632
- # A candidate is a Mapping of an object type which may become a Composition (a table, in docgraph-speak)
633
- class Candidate
634
- attr_reader :mapping, :is_document, :is_triple, :is_tentative
635
- attr_accessor :full_absorption
636
-
637
- def initialize compositor, mapping
638
- @compositor = compositor
639
- @mapping = mapping
640
- end
641
-
642
- def object_type
643
- @mapping.object_type
644
- end
645
-
646
- # References from us are things we can own (non-Mappings) or have a unique forward absorption for
647
- def references_from
648
- @mapping.all_member.select{|m| !m.is_a?(MM::Absorption) or !m.forward_absorption && m.parent_role.is_unique }
649
- end
650
- alias_method :rf, :references_from
651
-
652
- # References to us are reverse absorptions where the forward absorption can absorb us
653
- def references_to
654
- @mapping.all_member.select{|m| m.is_a?(MM::Absorption) and f = m.forward_absorption and f.parent_role.is_unique}
655
- end
656
- alias_method :rt, :references_to
657
-
658
- def has_references
659
- @mapping.all_member.select{|m| m.is_a?(MM::Absorption) }
660
- end
661
-
662
- def definitely_not_document
663
- @is_tentative = @is_document = false
664
- end
665
-
666
- def definitely_document
667
- @is_tentative = false
668
- @is_document = true
669
- @is_triple = false
670
- end
671
-
672
- def definitely_not_triple
673
- @is_tentative = @is_triple = false
674
- end
675
-
676
- def definitely_triple
677
- @is_tentative = false
678
- @is_triple = true
679
- @is_document = false
680
- end
681
-
682
- def probably_not_document
683
- @is_tentative = true
684
- @is_document = false
685
- end
686
-
687
- def probably_document
688
- @is_tentative = @is_document = true
689
- end
690
-
691
- def assign_default composition
692
- o = object_type
693
- if o.is_separate
694
- trace :docgraph_defaults, "#{o.name} is a document because it's declared independent or separate"
695
- definitely_document
696
- return
697
- end
698
-
699
- if o.concept.all_concept_annotation.detect{|ca| ca.mapping_annotation =~ TRIPLE_ANNOTATION}
700
- trace :docgraph_defaults, "#{o.name} is a triple because it's declared triple"
701
- definitely_triple
702
- return
703
- end
704
-
705
- case o
706
- when MM::ValueType
707
- if o.is_auto_assigned
708
- trace :docgraph_defaults, "#{o.name} is not a document because it is auto assigned"
709
- definitely_not_document
710
- elsif references_from.size > 0
711
- trace :docgraph_defaults, "#{o.name} is a document because it has references to absorb"
712
- definitely_document
713
- else
714
- trace :docgraph_defaults, "#{o.name} is not a document because it will be absorbed wherever needed"
715
- definitely_not_document
716
- end
717
-
718
- when MM::EntityType
719
- if references_to.empty? and
720
- !references_from.detect do |absorption| # detect whether anything can absorb this entity type
721
- absorption.is_a?(MM::Mapping) && absorption.parent_role.is_unique # DG && absorption.child_role.is_unique
722
- end
723
- trace :docgraph_defaults, "#{o.name} is a document because it has nothing to absorb it"
724
- definitely_document
725
- return
726
- end
727
-
728
- # its a triple if this is an objectified fact type that has a uniqueness constraint of size = 2
729
- if o.fact_type
730
- # List the UCs on this fact type:
731
- all_uniqueness_constraints =
732
- o.fact_type.all_role.map do |fact_role|
733
- fact_role.all_role_ref.map do |rr|
734
- rr.role_sequence.all_presence_constraint.select { |pc| pc.max_frequency == 1 }
735
- end
736
- end.flatten.uniq
737
-
738
- if all_uniqueness_constraints.detect do |uc|
739
- (arr = uc.role_sequence.all_role_ref).size == 2 and arr[0].role.object_type.is_a?(MM::EntityType) and arr[1].role.object_type.is_a?(MM::EntityType)
740
- end
741
- trace :docgraph_defaults, "#{o.name} is a triple because is an objectified fact type with uniqueness contraint of 2"
742
- definitely_triple
743
- return
744
- end
745
- end
746
-
747
- if !o.supertypes.empty?
748
- # We know that this entity type is not a separate or partitioned subtype, so a supertype that can absorb us does
749
- identifying_fact_type = o.all_type_inheritance_as_subtype.detect{|ti| ti.provides_identification}
750
- if identifying_fact_type
751
- fact_type = identifying_fact_type
752
- else
753
- if o.all_type_inheritance_as_subtype.size > 1
754
- trace :docgraph_defaults, "REVISIT: #{o.name} cannot be absorbed into a supertype that doesn't also absorb all our other supertypes (or is absorbed into one of its supertypes that does)"
755
- end
756
- fact_type = o.all_type_inheritance_as_subtype.to_a[0]
757
- end
758
-
759
- absorbing_ref = mapping.all_member.detect{|m| m.is_a?(MM::Absorption) && m.child_role.fact_type == fact_type}
760
-
761
- absorbing_ref = absorbing_ref.flip! if absorbing_ref.reverse_absorption # We were forward, but the other end must be
762
- absorbing_ref = absorbing_ref.forward_absorption
763
- self.full_absorption =
764
- o.constellation.FullAbsorption(composition: composition, absorption: absorbing_ref, object_type: o)
765
- trace :docgraph_defaults, "Supertype #{fact_type.supertype_role.name} fully absorbs subtype #{o.name} via #{absorbing_ref.inspect}"
766
- definitely_not_document
767
- return
768
- end # subtype
769
-
770
- # If the preferred_identifier consists of a ValueType that's auto-assigned,
771
- # that can only happen in one document, which controls the sequence.
772
- auto_assigned_identifying_role_player = nil
773
- pi_role_refs = o.preferred_identifier.role_sequence.all_role_ref
774
- if pi_role_refs.size == 1 and
775
- rr = pi_role_refs.single and
776
- (v = rr.role.object_type).is_a?(MM::ValueType) and
777
- v.is_auto_assigned == 'commit'
778
- auto_assigned_identifying_role_player = v
779
- end
780
- if (@compositor.options['single_sequence'] || references_to.size > 1) and auto_assigned_identifying_role_player # Can be absorbed in more than one place
781
- trace :docgraph_defaults, "#{o.name} must be a document to support its auto-assigned identifier #{auto_assigned_identifying_role_player.name}"
782
- definitely_document
783
- return
784
- end
785
-
786
- trace :docgraph_defaults, "#{o.name} is initially presumed to be a document"
787
- probably_document
788
-
789
- end # case
790
- end
791
-
792
- end
793
-
794
- end
795
-
796
- publish_compositor(DocGraph)
797
- end
798
- end