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.
- checksums.yaml +4 -4
- data/activefacts-compositions.gemspec +2 -2
- data/lib/activefacts/compositions/binary.rb +1 -1
- data/lib/activefacts/compositions/compositor.rb +16 -12
- data/lib/activefacts/compositions/datavault.rb +110 -115
- data/lib/activefacts/compositions/relational.rb +137 -94
- data/lib/activefacts/compositions/staging.rb +89 -27
- data/lib/activefacts/compositions/traits/datavault.rb +116 -49
- data/lib/activefacts/compositions/traits/rails.rb +2 -2
- data/lib/activefacts/compositions/version.rb +1 -1
- data/lib/activefacts/generator/doc/cwm.rb +6 -18
- data/lib/activefacts/generator/doc/ldm.rb +1 -1
- data/lib/activefacts/generator/etl/unidex.rb +341 -0
- data/lib/activefacts/generator/oo.rb +31 -14
- data/lib/activefacts/generator/rails/models.rb +6 -5
- data/lib/activefacts/generator/rails/schema.rb +5 -9
- data/lib/activefacts/generator/ruby.rb +2 -2
- data/lib/activefacts/generator/sql/mysql.rb +3 -184
- data/lib/activefacts/generator/sql/oracle.rb +3 -152
- data/lib/activefacts/generator/sql/postgres.rb +3 -145
- data/lib/activefacts/generator/sql/server.rb +3 -126
- data/lib/activefacts/generator/sql.rb +54 -422
- data/lib/activefacts/generator/summary.rb +15 -6
- data/lib/activefacts/generator/traits/expr.rb +41 -0
- data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
- data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
- data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
- data/lib/activefacts/generator/traits/sql/server.rb +262 -0
- data/lib/activefacts/generator/traits/sql.rb +538 -0
- metadata +13 -8
- data/lib/activefacts/compositions/docgraph.rb +0 -798
- 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
|