activefacts 1.5.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4c7be7ca9e116d36232a6e9c0bcf28f0f4a63eb
4
- data.tar.gz: a3c13e8e437aa028760eff29cd05df07b0017a91
3
+ metadata.gz: 887391f1fb780fae6998c9ea40bc158d1b68638d
4
+ data.tar.gz: ca958351f21cf0b5bf81116982fa9604735b2a42
5
5
  SHA512:
6
- metadata.gz: 49878c205424a5534e811af19bf09ebe0e3c8aefbd3127a1ee0695d3a5c77eec2beb74715455ff32d7bde613e8a1eff8241dc35ba77a79915770e1ee68c60ba0
7
- data.tar.gz: 4396ee06e34a48236e858274010356123f66847686a04763b4b4992283ae30f8dd6b0d96e7086803339a97f9ffbc2c158d2563a8840a288c330623f11680de8b
6
+ metadata.gz: d708a24d6ebc6db435c50a2f5aa3a3f7f3dcf416fe7a1104dc9deec10bfb0128853a39ea0e31688bcb49914acd45a3df7298409d7410f8ea36888b019cf09672
7
+ data.tar.gz: aa44870543b7485a003e2a1ffe3bf12aa73e5f1c3516825a62819dd9c2eed2ddc47bffdf56b01e8af8a0847520ce68da66e6340bd83db628f80e7a8fa89e7611
@@ -6,6 +6,7 @@ vocabulary Metamodel;
6
6
  Adjective is written as String(64);
7
7
  Agent Name is written as String;
8
8
  Aggregate Code is written as String(32);
9
+ Annotation is written as String;
9
10
  Assimilation is written as String restricted to {'absorbed', 'partitioned', 'separate'};
10
11
  Context Note Kind is written as String restricted to {'as_opposed_to', 'because', 'so_that', 'to_avoid'};
11
12
  Date is written as Date;
@@ -31,7 +32,6 @@ Rotation Setting is written as String restricted to {'left', 'right'};
31
32
  Scale is written as Unsigned Integer(32);
32
33
  Subscript is written as Unsigned Integer(16);
33
34
  Text is written as String(256);
34
- Topic Name is written as Name;
35
35
  Transaction Phase is written as String restricted to {'assert', 'commit'};
36
36
  X is written as Signed Integer(32);
37
37
  Y is written as Signed Integer(32);
@@ -56,6 +56,8 @@ Coefficient is identified by Numerator and Denominator and Coefficient is precis
56
56
  Concept is identified by Guid where
57
57
  Concept has one Guid,
58
58
  Guid is of at most one Concept;
59
+ Concept has mapping-Annotation,
60
+ Annotation applies to at most one Concept;
59
61
 
60
62
  Constraint is identified by Concept where
61
63
  Constraint is an instance of one Concept;
@@ -124,6 +126,8 @@ Role is identified by Fact Type and Ordinal where
124
126
  Fact Type contains Role,
125
127
  Role fills one Ordinal,
126
128
  Ordinal applies to Role;
129
+ Link Fact Type has one implying-Role,
130
+ implying-Role implies at most one Link Fact Type;
127
131
  Ring Constraint has at most one other-Role,
128
132
  other-Role is of Ring Constraint;
129
133
  Role is an instance of one Concept;
@@ -133,12 +137,6 @@ Role is of Ring Constraint,
133
137
  Role has at most one role-Name,
134
138
  role-Name is name of at least one Role;
135
139
 
136
- Role Proxy is a kind of Role;
137
- Link Fact Type has one Role Proxy,
138
- Role Proxy is of at most one Link Fact Type;
139
- Role Proxy is for at most one Role,
140
- Role has at most one Role Proxy;
141
-
142
140
  Role Sequence is identified by Guid where
143
141
  Role Sequence has one Guid,
144
142
  Guid is of at most one Role Sequence;
@@ -162,11 +160,23 @@ Shape is identified by Guid where
162
160
  Shape is at at most one Location;
163
161
  Shape is expanded;
164
162
 
163
+ Step is identified by Guid where
164
+ Step has one Guid,
165
+ Guid is of at most one Step;
166
+ Step falls under at most one Alternative Set,
167
+ Alternative Set covers at least one Step;
168
+ Step specifies one Fact Type,
169
+ Fact Type directs Step;
170
+ Step is disallowed;
171
+ Step is optional;
172
+
165
173
  Subset Constraint is a kind of Set Constraint;
166
174
  Subset Constraint covers one subset-Role Sequence;
167
175
  Subset Constraint covers one superset-Role Sequence;
168
176
 
169
- Topic is identified by its Name;
177
+ Topic is identified by topic-Name where
178
+ Topic has one topic-Name,
179
+ topic-Name is of at most one Topic;
170
180
  Concept belongs to at most one Topic,
171
181
  Topic contains Concept;
172
182
 
@@ -202,6 +212,8 @@ Variable is identified by Query and Ordinal where
202
212
  Variable has one Ordinal position;
203
213
  Variable projects at most one Role (as Projection),
204
214
  Projection is projected from at most one Variable;
215
+ Variable (as Objectification Variable) matches nesting over at most one Step,
216
+ Step nests as at most one Objectification Variable;
205
217
  Variable has at most one Subscript,
206
218
  Subscript is of Variable;
207
219
  Variable is bound to at most one Value;
@@ -287,8 +299,9 @@ Objectified Fact Type Name Shape is for one Fact Type Shape,
287
299
  Fact Type Shape has at most one Objectified Fact Type Name Shape;
288
300
 
289
301
  Play is where
290
- Variable is restricted by Role,
291
- Role controls Variable;
302
+ Variable is restricted by Role of Step,
303
+ Step contains Role restricting one Variable;
304
+ Play is input;
292
305
 
293
306
  Population is identified by Vocabulary and Name where
294
307
  Vocabulary includes Population,
@@ -325,7 +338,8 @@ Role Ref is where
325
338
  Role is in Role Sequence in one Ordinal place,
326
339
  Role Sequence includes Role in Ordinal place,
327
340
  Role has Ordinal place in Role Sequence;
328
- Play projects at most one Role Ref;
341
+ Play projects at most one Role Ref,
342
+ Role Ref is projected from at most one Play;
329
343
  Role Ref has at most one leading-Adjective;
330
344
  Role Ref has at most one trailing-Adjective;
331
345
 
@@ -342,18 +356,6 @@ Set Equality Constraint is a kind of Set Comparison Constraint;
342
356
  Set Exclusion Constraint is a kind of Set Comparison Constraint;
343
357
  Set Exclusion Constraint is mandatory;
344
358
 
345
- Step is identified by input-Play and output-Play where
346
- Step has one input-Play,
347
- Step has at most one output-Play;
348
- Step falls under at most one Alternative Set,
349
- Alternative Set covers at least one Step;
350
- Step specifies one Fact Type,
351
- Fact Type directs Step;
352
- Step involves incidental-Play,
353
- Play is incidentally involved in at most one Step;
354
- Step is disallowed;
355
- Step is optional;
356
-
357
359
  Value Constraint Shape is a kind of Constraint Shape;
358
360
  Role Display has at most one Value Constraint Shape,
359
361
  Value Constraint Shape is for at most one Role Display;
@@ -394,15 +396,15 @@ Value Type has at most one Value Constraint,
394
396
  Value Type is subtype of at most one super-Value Type (as Supertype) [acyclic, transitive],
395
397
  Supertype is supertype of Value Type;
396
398
 
397
- Facet is where
399
+ Value Type Parameter is where
398
400
  Value Type has facet called Name,
399
401
  Name is a facet of Value Type;
400
- Facet requires value of one facet-Value Type;
402
+ Value Type Parameter requires value of one facet-Value Type;
401
403
 
402
- Facet Restriction is where
403
- Value Type applies Facet,
404
- Facet applies to Value Type;
405
- Facet Restriction has one Value;
404
+ Value Type Parameter Restriction is where
405
+ Value Type receives Value Type Parameter,
406
+ Value Type Parameter applies to Value Type;
407
+ Value Type Parameter Restriction has one Value;
406
408
 
407
409
  Implicit Boolean Value Type is a kind of Value Type;
408
410
 
@@ -441,27 +443,24 @@ either Value Constraint constrains Value Type or Value Constraint applies to Rol
441
443
  for each Instance at most one of these holds:
442
444
  Instance has Value,
443
445
  Instance objectifies Fact;
444
- for each Play at most one of these holds:
445
- Step (1) has input-Play,
446
- Play is incidentally involved in Step;
447
- for each Play at most one of these holds:
448
- Step (1) has output-Play,
449
- Play is incidentally involved in Step;
450
446
  Role Value is of Instance that is of Object Type
451
447
  if and only if
452
448
  Role Value is of Role that is played by Object Type;
453
449
  Role Value fulfils Fact that is of Fact Type
454
450
  if and only if
455
451
  Role Value is of Role that belongs to Fact Type;
452
+ Step specifies Fact Type that contains Role
453
+ if and only if
454
+ Step contains Role restricting Variable;
456
455
  Variable is for Object Type that plays Role
457
456
  if and only if
458
- Variable is restricted by Role;
457
+ Variable is restricted by Role of Step;
459
458
  Presence Constraint is preferred identifier
460
459
  only if Presence Constraint has max Frequency;
461
- Step involves incidental Play
462
- only if Step has output Play;
463
460
  Value Type has Scale
464
461
  only if Value Type has Length;
462
+ Variable matches nesting over Step
463
+ only if Variable is for Object Type and Step specifies Fact Type;
465
464
  either Agreement was reached by Agent or Agreement was on Date;
466
465
  each Concept occurs at most one time in
467
466
  Object Type is an instance of Concept;
@@ -479,8 +478,6 @@ each Presence Constraint occurs at least one time in
479
478
  Presence Constraint has min Frequency(2),
480
479
  Presence Constraint has max Frequency(1),
481
480
  Presence Constraint is mandatory;
482
- each Role Ref occurs at most one time in
483
- Play projects Role Ref;
484
481
  each Role Sequence occurs at least one time in
485
482
  Role Sequence in Ordinal position includes Role;
486
483
  each Set Comparison Constraint occurs at least 2 times in
@@ -174,10 +174,20 @@ module ActiveFacts
174
174
  end
175
175
 
176
176
  rule mapping_pragma
177
- (independent / separate / partitioned / personal / feminine / masculine /
178
- was s names:(id s)+ { def text_value; [ was.text_value, names.elements.map{|n|n.text_value} ]; end }
179
- )
180
- { def value; text_value; end }
177
+ was s names:(id s)+
178
+ { # Old or previous name of an object type:
179
+ def value
180
+ [ was.text_value, names.elements.map{|n|n.text_value} ]
181
+ end
182
+ }
183
+ /
184
+ head:id tail:(s id)*
185
+ { # A sequence of one or more words denoting a pragma:
186
+ def value
187
+ ([head]+tail.elements.map(&:id)).
188
+ map(&:text_value)*' '
189
+ end
190
+ }
181
191
  end
182
192
 
183
193
  rule entity_clauses
@@ -31,13 +31,16 @@ module ActiveFacts
31
31
  def compile
32
32
  @entity_type = @vocabulary.valid_entity_type_name(@name) ||
33
33
  @constellation.EntityType(@vocabulary, @name, :concept => :new)
34
- @entity_type.is_independent = true if (@pragmas.include? 'independent')
34
+ @entity_type.is_independent = true if @pragmas.delete('independent')
35
35
 
36
36
  # REVISIT: CQL needs a way to indicate whether subtype migration can occur.
37
37
  # For example by saying "Xyz is a role of Abc".
38
38
  @supertypes.each_with_index do |supertype_name, i|
39
39
  add_supertype(supertype_name, @identification || i > 0)
40
40
  end
41
+ @pragmas.each do |p|
42
+ @constellation.Annotation(p, :concept => @entity_type.concept)
43
+ end if @pragmas
41
44
 
42
45
  context = CompilationContext.new(@vocabulary)
43
46
 
@@ -237,7 +240,9 @@ module ActiveFacts
237
240
 
238
241
  inheritance_fact = @constellation.TypeInheritance(@entity_type, supertype, :concept => :new)
239
242
 
240
- assimilations = @pragmas.select { |p| ['absorbed', 'separate', 'partitioned'].include? p}
243
+ assimilation_pragmas = ['absorbed', 'separate', 'partitioned']
244
+ assimilations = @pragmas.select { |p| assimilation_pragmas.include? p}
245
+ @pragmas -= assimilation_pragmas
241
246
  raise "Conflicting assimilation pragmas #{assimilations*', '}" if assimilations.size > 1
242
247
  inheritance_fact.assimilation = assimilations[0]
243
248
 
@@ -169,10 +169,10 @@ module ActiveFacts
169
169
  if @pragmas
170
170
  @entity_type.is_independent = true if @pragmas.delete('independent')
171
171
  end
172
- if @pragmas && @pragmas.size > 0
173
- $stderr.puts "Mapping pragmas #{@pragmas.inspect} are ignored for objectified fact type #{@name}"
174
- end
175
172
  end
173
+ @pragmas.each do |p|
174
+ @constellation.Annotation(p, :concept => @fact_type.concept)
175
+ end if @pragmas
176
176
 
177
177
  @clauses.each do |clause|
178
178
  next unless clause.context_note
@@ -113,7 +113,10 @@ module ActiveFacts
113
113
  # Create and initialise the ValueType:
114
114
  vt = @vocabulary.valid_value_type_name(@name) ||
115
115
  @constellation.ValueType(@vocabulary, @name, :concept => :new)
116
- vt.is_independent = true if (@pragmas.include? 'independent')
116
+ vt.is_independent = true if @pragmas.delete('independent')
117
+ @pragmas.each do |p|
118
+ @constellation.Annotation(p, :concept => vt.concept)
119
+ end if @pragmas
117
120
  vt.supertype = base_type if base_type
118
121
  vt.length = length if length
119
122
  vt.scale = scale if scale
@@ -165,7 +165,8 @@ module ActiveFacts
165
165
  "(" + length.to_s + (scale ? ", #{scale}" : "") + ")"
166
166
  end
167
167
  }"
168
- identity = column == identity_column ? " IDENTITY" : ""
168
+ # Emit IDENTITY for auto-assigned columns, unless it's assigned at assert:
169
+ identity = column == identity_column && column.references[-1].to.transaction_phase != 'assert' ? " IDENTITY" : ""
169
170
  null = (column.is_mandatory ? "NOT " : "") + "NULL"
170
171
  check = check_clause(name, constraints)
171
172
  comment = column.comment
@@ -12,167 +12,167 @@ module ActiveFacts
12
12
  class ObjectType
13
13
 
14
14
  def add_surrogate type_name = 'Auto Counter', suffix = 'ID'
15
- # Find or assert the surrogate value type
16
- auto_counter = vocabulary.valid_value_type_name(type_name) ||
17
- constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :concept => :new)
18
-
19
- # Create a subtype to identify this entity type:
20
- vt_name = self.name + ' '+suffix
21
- my_id = @vocabulary.valid_value_type_name(vt_name) ||
22
- constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :concept => :new, :supertype => auto_counter)
23
-
24
- # Create a fact type
25
- identifying_fact_type = constellation.FactType(:concept => :new)
26
- my_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 0, :object_type => self)
27
- @injected_surrogate_role = my_role
28
- id_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 1, :object_type => my_id)
29
-
30
- # Create a reading (which needs a RoleSequence)
31
- reading = constellation.Reading(
32
- :fact_type => identifying_fact_type,
33
- :ordinal => 0,
34
- :role_sequence => [:new],
35
- :text => "{0} has {1}"
36
- )
37
- constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role)
38
- constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role)
39
-
40
- # Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs)
41
- one_id = constellation.PresenceConstraint(
42
- :concept => :new,
43
- :vocabulary => vocabulary,
44
- :name => self.name+'HasOne'+suffix,
45
- :role_sequence => [:new],
46
- :is_mandatory => true,
47
- :min_frequency => 1,
48
- :max_frequency => 1,
49
- :is_preferred_identifier => false
50
- )
51
- @constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role)
52
-
53
- one_me = constellation.PresenceConstraint(
54
- :concept => :new,
55
- :vocabulary => vocabulary,
56
- :name => self.name+suffix+'IsOfOne'+self.name,
57
- :role_sequence => [:new],
58
- :is_mandatory => false,
59
- :min_frequency => 0,
60
- :max_frequency => 1,
61
- :is_preferred_identifier => true
62
- )
63
- @constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role)
15
+ # Find or assert the surrogate value type
16
+ auto_counter = vocabulary.valid_value_type_name(type_name) ||
17
+ constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :concept => :new)
18
+
19
+ # Create a subtype to identify this entity type:
20
+ vt_name = self.name + ' '+suffix
21
+ my_id = @vocabulary.valid_value_type_name(vt_name) ||
22
+ constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :concept => :new, :supertype => auto_counter)
23
+
24
+ # Create a fact type
25
+ identifying_fact_type = constellation.FactType(:concept => :new)
26
+ my_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 0, :object_type => self)
27
+ @injected_surrogate_role = my_role
28
+ id_role = constellation.Role(:concept => :new, :fact_type => identifying_fact_type, :ordinal => 1, :object_type => my_id)
29
+
30
+ # Create a reading (which needs a RoleSequence)
31
+ reading = constellation.Reading(
32
+ :fact_type => identifying_fact_type,
33
+ :ordinal => 0,
34
+ :role_sequence => [:new],
35
+ :text => "{0} has {1}"
36
+ )
37
+ constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role)
38
+ constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role)
39
+
40
+ # Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs)
41
+ one_id = constellation.PresenceConstraint(
42
+ :concept => :new,
43
+ :vocabulary => vocabulary,
44
+ :name => self.name+'HasOne'+suffix,
45
+ :role_sequence => [:new],
46
+ :is_mandatory => true,
47
+ :min_frequency => 1,
48
+ :max_frequency => 1,
49
+ :is_preferred_identifier => false
50
+ )
51
+ @constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role)
52
+
53
+ one_me = constellation.PresenceConstraint(
54
+ :concept => :new,
55
+ :vocabulary => vocabulary,
56
+ :name => self.name+suffix+'IsOfOne'+self.name,
57
+ :role_sequence => [:new],
58
+ :is_mandatory => false,
59
+ :min_frequency => 0,
60
+ :max_frequency => 1,
61
+ :is_preferred_identifier => true
62
+ )
63
+ @constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role)
64
64
  end
65
65
  end
66
66
 
67
67
  class ValueType
68
68
  def needs_surrogate
69
- !is_auto_assigned
69
+ !is_auto_assigned
70
70
  end
71
71
 
72
72
  def inject_surrogate
73
- trace :transform_surrogate, "Adding surrogate ID to Value Type #{name}"
74
- add_surrogate('Auto Counter', 'ID')
73
+ trace :transform_surrogate, "Adding surrogate ID to Value Type #{name}"
74
+ add_surrogate('Auto Counter', 'ID')
75
75
  end
76
76
  end
77
77
 
78
78
  class EntityType
79
79
  def identifying_refs_from
80
- pi = preferred_identifier
81
- rrs = pi.role_sequence.all_role_ref
82
-
83
- # REVISIT: This is actually a ref to us, not from
84
- # if absorbed_via
85
- # return [absorbed_via]
86
- # end
87
-
88
- rrs.map do |rr|
89
- r = references_from.detect{|ref| rr.role == ref.to_role }
90
- raise "failed to find #{name} identifying reference for #{rr.role.object_type.name} in #{references_from.inspect}" unless r
91
- r
92
- end
80
+ pi = preferred_identifier
81
+ rrs = pi.role_sequence.all_role_ref
82
+
83
+ # REVISIT: This is actually a ref to us, not from
84
+ # if absorbed_via
85
+ # return [absorbed_via]
86
+ # end
87
+
88
+ rrs.map do |rr|
89
+ r = references_from.detect{|ref| rr.role == ref.to_role }
90
+ raise "failed to find #{name} identifying reference for #{rr.role.object_type.name} in #{references_from.inspect}" unless r
91
+ r
92
+ end
93
93
  end
94
94
 
95
95
  def needs_surrogate
96
96
 
97
- # A recursive proc to replace any reference to an Entity Type by its identifying references:
98
- trace :transform_surrogate_expansion, "Expanding key for #{name}"
99
- substitute_identifying_refs = proc do |object|
100
- if ref = object.absorbed_via
101
- # This shouldn't be necessary, but see the absorbed_via comment above.
102
- absorbed_into = ref.from
103
- trace :transform_surrogate_expansion, "recursing to handle absorption of #{object.name} into #{absorbed_into.name}"
104
- [substitute_identifying_refs.call(absorbed_into)]
105
- else
106
- irf = object.identifying_refs_from
107
- trace :transform_surrogate_expansion, "Iterating for #{object.name} over #{irf.inspect}" do
108
- irf.each_with_index do |ref, i|
109
- next if ref.is_unary
110
- next if ref.to_role.object_type.kind_of?(ActiveFacts::Metamodel::ValueType)
111
- recurse_to = ref.to_role.object_type
112
-
113
- trace :transform_surrogate_expansion, "#{i}: recursing to expand #{recurse_to.name} key in #{ref}" do
114
- irf[i] = substitute_identifying_refs.call(recurse_to)
115
- end
116
- end
117
- end
118
- irf
119
- end
120
- end
121
- irf = substitute_identifying_refs.call(self)
122
-
123
- trace :transform_surrogate, "Does #{name} need a surrogate? it's identified by #{irf.inspect}" do
124
-
125
- pk_fks = identifying_refs_from.map do |ref|
126
- ref.to && ref.to.is_table ? ref.to : nil
127
- end
128
-
129
- irf.flatten!
130
-
131
- # Multi-part identifiers are only allowed if:
132
- # * each part is a foreign key (i.e. it's a join table),
133
- # * there are no other columns (that might require updating) and
134
- # * the object is not the target of a foreign key:
135
- if irf.size >= 2
136
- if pk_fks.include?(nil)
137
- trace :transform_surrogate, "#{self.name} needs a surrogate because its multi-part key contains a non-table"
138
- return true
139
- elsif references_to.size != 0
140
- trace :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect} but is also an FK target"
141
- return true
142
- elsif (references_from-identifying_refs_from).size > 0
143
- # There are other attributes to worry about
144
- return true
145
- else
146
- trace :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect}"
147
- return false
148
- end
149
- return true
150
- end
151
-
152
- # Single-part key. It must be an Auto Counter, or we will add a surrogate
153
-
154
- identifying_type = irf[0].to
155
- if identifying_type.needs_surrogate
156
- trace :transform_surrogate, "#{self.name} needs a surrogate because #{irf[0].to.name} is not an AutoCounter, but #{identifying_type.supertypes_transitive.map(&:name).inspect}"
157
- return true
158
- end
159
-
160
- false
161
- end
97
+ # A recursive proc to replace any reference to an Entity Type by its identifying references:
98
+ trace :transform_surrogate_expansion, "Expanding key for #{name}"
99
+ substitute_identifying_refs = proc do |object|
100
+ if ref = object.absorbed_via
101
+ # This shouldn't be necessary, but see the absorbed_via comment above.
102
+ absorbed_into = ref.from
103
+ trace :transform_surrogate_expansion, "recursing to handle absorption of #{object.name} into #{absorbed_into.name}"
104
+ [substitute_identifying_refs.call(absorbed_into)]
105
+ else
106
+ irf = object.identifying_refs_from
107
+ trace :transform_surrogate_expansion, "Iterating for #{object.name} over #{irf.inspect}" do
108
+ irf.each_with_index do |ref, i|
109
+ next if ref.is_unary
110
+ next if ref.to_role.object_type.kind_of?(ActiveFacts::Metamodel::ValueType)
111
+ recurse_to = ref.to_role.object_type
112
+
113
+ trace :transform_surrogate_expansion, "#{i}: recursing to expand #{recurse_to.name} key in #{ref}" do
114
+ irf[i] = substitute_identifying_refs.call(recurse_to)
115
+ end
116
+ end
117
+ end
118
+ irf
119
+ end
120
+ end
121
+ irf = substitute_identifying_refs.call(self)
122
+
123
+ trace :transform_surrogate, "Does #{name} need a surrogate? it's identified by #{irf.inspect}" do
124
+
125
+ pk_fks = identifying_refs_from.map do |ref|
126
+ ref.to && ref.to.is_table ? ref.to : nil
127
+ end
128
+
129
+ irf.flatten!
130
+
131
+ # Multi-part identifiers are only allowed if:
132
+ # * each part is a foreign key (i.e. it's a join table),
133
+ # * there are no other columns (that might require updating) and
134
+ # * the object is not the target of a foreign key:
135
+ if irf.size >= 2
136
+ if pk_fks.include?(nil)
137
+ trace :transform_surrogate, "#{self.name} needs a surrogate because its multi-part key contains a non-table"
138
+ return true
139
+ elsif references_to.size != 0
140
+ trace :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect} but is also an FK target"
141
+ return true
142
+ elsif (references_from-identifying_refs_from).size > 0
143
+ # There are other attributes to worry about
144
+ return true
145
+ else
146
+ trace :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect}"
147
+ return false
148
+ end
149
+ return true
150
+ end
151
+
152
+ # Single-part key. It must be an Auto Counter, or we will add a surrogate
153
+
154
+ identifying_type = irf[0].to
155
+ if identifying_type.needs_surrogate
156
+ trace :transform_surrogate, "#{self.name} needs a surrogate because #{irf[0].to.name} is not an AutoCounter, but #{identifying_type.supertypes_transitive.map(&:name).inspect}"
157
+ return true
158
+ end
159
+
160
+ false
161
+ end
162
162
  end
163
163
 
164
164
  def inject_surrogate
165
- trace :transform_surrogate, "Injecting a surrogate key into #{self.name}"
165
+ trace :transform_surrogate, "Injecting a surrogate key into #{self.name}"
166
166
 
167
- # Disable the preferred identifier:
168
- pi = preferred_identifier
169
- trace :transform_surrogate, "pi for #{name} was '#{pi.describe}'"
170
- pi.is_preferred_identifier = false
171
- @preferred_identifier = nil # Kill the cache
167
+ # Disable the preferred identifier:
168
+ pi = preferred_identifier
169
+ trace :transform_surrogate, "pi for #{name} was '#{pi.describe}'"
170
+ pi.is_preferred_identifier = false
171
+ @preferred_identifier = nil # Kill the cache
172
172
 
173
- add_surrogate
173
+ add_surrogate
174
174
 
175
- trace :transform_surrogate, "pi for #{name} is now '#{preferred_identifier.describe}'"
175
+ trace :transform_surrogate, "pi for #{name} is now '#{preferred_identifier.describe}'"
176
176
  end
177
177
 
178
178
  end
@@ -181,8 +181,8 @@ module ActiveFacts
181
181
  module Persistence
182
182
  class Column
183
183
  def is_injected_surrogate
184
- references.size == 1 and
185
- references[0].from_role == references[0].from.injected_surrogate_role
184
+ references.size == 1 and
185
+ references[0].from_role == references[0].from.injected_surrogate_role
186
186
  end
187
187
  end
188
188
  end
@@ -190,22 +190,22 @@ module ActiveFacts
190
190
  module Generate #:nodoc:
191
191
  module Transform #:nodoc:
192
192
  class Surrogate
193
- def initialize(vocabulary, *options)
194
- @vocabulary = vocabulary
195
- end
196
-
197
- def generate(out = $stdout)
198
- @out = out
199
- injections =
200
- @vocabulary.tables.select do |table|
201
- table.needs_surrogate
202
- end
203
- injections.each do |table|
204
- table.inject_surrogate
205
- end
206
-
207
- @vocabulary.decide_tables
208
- end
193
+ def initialize(vocabulary, *options)
194
+ @vocabulary = vocabulary
195
+ end
196
+
197
+ def generate(out = $stdout)
198
+ @out = out
199
+ injections =
200
+ @vocabulary.tables.select do |table|
201
+ table.needs_surrogate
202
+ end
203
+ injections.each do |table|
204
+ table.inject_surrogate
205
+ end
206
+
207
+ @vocabulary.decide_tables
208
+ end
209
209
  end
210
210
  end
211
211
  end