activefacts 1.6.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +60 -0
  8. data/Rakefile +3 -80
  9. data/activefacts.gemspec +36 -0
  10. data/bin/afgen +4 -2
  11. data/bin/cql +5 -1
  12. data/lib/activefacts.rb +3 -12
  13. data/lib/activefacts/{vocabulary/query_evaluator.rb → query/evaluator.rb} +0 -0
  14. data/lib/activefacts/version.rb +2 -2
  15. metadata +48 -296
  16. data/History.txt +0 -4
  17. data/LICENSE +0 -19
  18. data/Manifest.txt +0 -165
  19. data/README.rdoc +0 -81
  20. data/css/offline.css +0 -3
  21. data/css/orm2.css +0 -124
  22. data/css/print.css +0 -8
  23. data/css/style-print.css +0 -357
  24. data/css/style.css +0 -387
  25. data/download.html +0 -110
  26. data/examples/CQL/Address.cql +0 -44
  27. data/examples/CQL/Blog.cql +0 -54
  28. data/examples/CQL/CompanyDirectorEmployee.cql +0 -56
  29. data/examples/CQL/Death.cql +0 -17
  30. data/examples/CQL/Diplomacy.cql +0 -48
  31. data/examples/CQL/Genealogy.cql +0 -98
  32. data/examples/CQL/Insurance.cql +0 -320
  33. data/examples/CQL/Marriage.cql +0 -18
  34. data/examples/CQL/Metamodel.cql +0 -493
  35. data/examples/CQL/Monogamy.cql +0 -24
  36. data/examples/CQL/MultiInheritance.cql +0 -22
  37. data/examples/CQL/NonRoleId.cql +0 -14
  38. data/examples/CQL/OddIdentifier.cql +0 -18
  39. data/examples/CQL/OilSupply.cql +0 -53
  40. data/examples/CQL/OneToOnes.cql +0 -17
  41. data/examples/CQL/Orienteering.cql +0 -111
  42. data/examples/CQL/PersonPlaysGame.cql +0 -18
  43. data/examples/CQL/RedundantDependency.cql +0 -34
  44. data/examples/CQL/SchoolActivities.cql +0 -33
  45. data/examples/CQL/SeparateSubtype.cql +0 -30
  46. data/examples/CQL/ServiceDirector.cql +0 -276
  47. data/examples/CQL/SimplestUnary.cql +0 -12
  48. data/examples/CQL/Supervision.cql +0 -34
  49. data/examples/CQL/WaiterTips.cql +0 -33
  50. data/examples/CQL/Warehousing.cql +0 -101
  51. data/examples/CQL/WindowInRoomInBldg.cql +0 -28
  52. data/examples/CQL/unit.cql +0 -474
  53. data/examples/index.html +0 -420
  54. data/examples/intro.html +0 -327
  55. data/examples/local.css +0 -24
  56. data/index.html +0 -111
  57. data/lib/activefacts/cql.rb +0 -35
  58. data/lib/activefacts/cql/CQLParser.treetop +0 -158
  59. data/lib/activefacts/cql/Context.treetop +0 -48
  60. data/lib/activefacts/cql/Expressions.treetop +0 -67
  61. data/lib/activefacts/cql/FactTypes.treetop +0 -358
  62. data/lib/activefacts/cql/Language/English.treetop +0 -315
  63. data/lib/activefacts/cql/LexicalRules.treetop +0 -253
  64. data/lib/activefacts/cql/ObjectTypes.treetop +0 -210
  65. data/lib/activefacts/cql/Rakefile +0 -14
  66. data/lib/activefacts/cql/Terms.treetop +0 -183
  67. data/lib/activefacts/cql/ValueTypes.treetop +0 -202
  68. data/lib/activefacts/cql/compiler.rb +0 -156
  69. data/lib/activefacts/cql/compiler/clause.rb +0 -1137
  70. data/lib/activefacts/cql/compiler/constraint.rb +0 -581
  71. data/lib/activefacts/cql/compiler/entity_type.rb +0 -457
  72. data/lib/activefacts/cql/compiler/expression.rb +0 -443
  73. data/lib/activefacts/cql/compiler/fact.rb +0 -390
  74. data/lib/activefacts/cql/compiler/fact_type.rb +0 -421
  75. data/lib/activefacts/cql/compiler/query.rb +0 -106
  76. data/lib/activefacts/cql/compiler/shared.rb +0 -161
  77. data/lib/activefacts/cql/compiler/value_type.rb +0 -174
  78. data/lib/activefacts/cql/nodes.rb +0 -49
  79. data/lib/activefacts/cql/parser.rb +0 -241
  80. data/lib/activefacts/dependency_analyser.rb +0 -182
  81. data/lib/activefacts/generate/absorption.rb +0 -70
  82. data/lib/activefacts/generate/composition.rb +0 -118
  83. data/lib/activefacts/generate/cql.rb +0 -714
  84. data/lib/activefacts/generate/dm.rb +0 -279
  85. data/lib/activefacts/generate/help.rb +0 -64
  86. data/lib/activefacts/generate/helpers/inject.rb +0 -16
  87. data/lib/activefacts/generate/helpers/oo.rb +0 -162
  88. data/lib/activefacts/generate/helpers/ordered.rb +0 -605
  89. data/lib/activefacts/generate/helpers/rails.rb +0 -57
  90. data/lib/activefacts/generate/html/glossary.rb +0 -461
  91. data/lib/activefacts/generate/json.rb +0 -337
  92. data/lib/activefacts/generate/null.rb +0 -32
  93. data/lib/activefacts/generate/rails/models.rb +0 -246
  94. data/lib/activefacts/generate/rails/schema.rb +0 -216
  95. data/lib/activefacts/generate/records.rb +0 -46
  96. data/lib/activefacts/generate/ruby.rb +0 -133
  97. data/lib/activefacts/generate/sql/mysql.rb +0 -280
  98. data/lib/activefacts/generate/sql/server.rb +0 -273
  99. data/lib/activefacts/generate/stats.rb +0 -69
  100. data/lib/activefacts/generate/text.rb +0 -27
  101. data/lib/activefacts/generate/topics.rb +0 -265
  102. data/lib/activefacts/generate/traits/datavault.rb +0 -241
  103. data/lib/activefacts/generate/traits/oo.rb +0 -73
  104. data/lib/activefacts/generate/traits/ordered.rb +0 -33
  105. data/lib/activefacts/generate/traits/ruby.rb +0 -210
  106. data/lib/activefacts/generate/transform/datavault.rb +0 -266
  107. data/lib/activefacts/generate/transform/surrogate.rb +0 -214
  108. data/lib/activefacts/generate/version.rb +0 -26
  109. data/lib/activefacts/input/cql.rb +0 -43
  110. data/lib/activefacts/input/orm.rb +0 -1636
  111. data/lib/activefacts/mapping/rails.rb +0 -132
  112. data/lib/activefacts/persistence.rb +0 -15
  113. data/lib/activefacts/persistence/columns.rb +0 -446
  114. data/lib/activefacts/persistence/foreignkey.rb +0 -187
  115. data/lib/activefacts/persistence/index.rb +0 -240
  116. data/lib/activefacts/persistence/object_type.rb +0 -198
  117. data/lib/activefacts/persistence/reference.rb +0 -434
  118. data/lib/activefacts/persistence/tables.rb +0 -380
  119. data/lib/activefacts/registry.rb +0 -11
  120. data/lib/activefacts/support.rb +0 -132
  121. data/lib/activefacts/vocabulary.rb +0 -9
  122. data/lib/activefacts/vocabulary/extensions.rb +0 -1348
  123. data/lib/activefacts/vocabulary/metamodel.rb +0 -570
  124. data/lib/activefacts/vocabulary/verbaliser.rb +0 -804
  125. data/script/txt2html +0 -71
  126. data/spec/absorption_spec.rb +0 -95
  127. data/spec/cql/comparison_spec.rb +0 -89
  128. data/spec/cql/context_spec.rb +0 -94
  129. data/spec/cql/contractions_spec.rb +0 -224
  130. data/spec/cql/deontic_spec.rb +0 -88
  131. data/spec/cql/entity_type_spec.rb +0 -320
  132. data/spec/cql/expressions_spec.rb +0 -66
  133. data/spec/cql/fact_type_matching_spec.rb +0 -338
  134. data/spec/cql/french_spec.rb +0 -21
  135. data/spec/cql/parser/bad_literals_spec.rb +0 -86
  136. data/spec/cql/parser/constraints_spec.rb +0 -19
  137. data/spec/cql/parser/entity_types_spec.rb +0 -106
  138. data/spec/cql/parser/expressions_spec.rb +0 -199
  139. data/spec/cql/parser/fact_types_spec.rb +0 -44
  140. data/spec/cql/parser/literals_spec.rb +0 -312
  141. data/spec/cql/parser/pragmas_spec.rb +0 -89
  142. data/spec/cql/parser/value_types_spec.rb +0 -42
  143. data/spec/cql/role_matching_spec.rb +0 -148
  144. data/spec/cql/samples_spec.rb +0 -244
  145. data/spec/cql_cql_spec.rb +0 -73
  146. data/spec/cql_dm_spec.rb +0 -136
  147. data/spec/cql_mysql_spec.rb +0 -69
  148. data/spec/cql_parse_spec.rb +0 -34
  149. data/spec/cql_ruby_spec.rb +0 -73
  150. data/spec/cql_sql_spec.rb +0 -72
  151. data/spec/cql_symbol_tables_spec.rb +0 -261
  152. data/spec/cqldump_spec.rb +0 -170
  153. data/spec/helpers/array_matcher.rb +0 -23
  154. data/spec/helpers/ctrl_c_support.rb +0 -52
  155. data/spec/helpers/diff_matcher.rb +0 -39
  156. data/spec/helpers/file_matcher.rb +0 -34
  157. data/spec/helpers/parse_to_ast_matcher.rb +0 -80
  158. data/spec/helpers/string_matcher.rb +0 -30
  159. data/spec/helpers/test_parser.rb +0 -15
  160. data/spec/norma_cql_spec.rb +0 -66
  161. data/spec/norma_ruby_spec.rb +0 -62
  162. data/spec/norma_ruby_sql_spec.rb +0 -107
  163. data/spec/norma_sql_spec.rb +0 -57
  164. data/spec/norma_tables_spec.rb +0 -95
  165. data/spec/ruby_api_spec.rb +0 -23
  166. data/spec/spec_helper.rb +0 -35
  167. data/spec/transform_surrogate_spec.rb +0 -59
  168. data/status.html +0 -138
  169. data/why.html +0 -60
@@ -1,434 +0,0 @@
1
- #
2
- # ActiveFacts Relational mapping and persistence.
3
- # Reference from one ObjectType to another, used to decide the relational mapping.
4
- #
5
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
- #
7
- # A Reference from one ObjectType to another is created for each many-1 or 1-1 relationship
8
- # (including subtyping), and also for a unary role (implicitly to Boolean object_type).
9
- # A 1-1 or subtyping reference should be created in only one direction, and may be flipped
10
- # if needed.
11
- #
12
- # A reference to a object_type that's a table or is fully absorbed into a table will
13
- # become a foreign key, otherwise it will absorb all that object_type's references.
14
- #
15
- # Reference objects update each object_type's list of the references *to* and *from* that object_type.
16
- #
17
- # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
18
- #
19
-
20
- module ActiveFacts
21
- module Persistence
22
-
23
- # This class contains the core data structure used in composing a relational schema.
24
- #
25
- # A Reference is *from* one ObjectType *to* another ObjectType, and relates to the *from_role* and the *to_role*.
26
- # When either ObjectType is an objectified fact type, the corresponding role is nil.
27
- # When the Reference from_role is of a unary fact type, there's no to_role or to ObjectType.
28
- # The final kind of Reference is a self-reference which is added to a ValueType that becomes a table.
29
- #
30
- # When the underlying fact type is a one-to-one (including an inheritance fact type), the Reference may be flipped.
31
- #
32
- # Each Reference has a name; an array of names in fact, in case of adjectives, etc.
33
- # Each Refererence can produce the reading of the underlying fact type.
34
- #
35
- # A Reference is indexed in the player's *references_from* and *references_to*, and flipping updates those.
36
- # Finally, a Reference may be marked as absorbing the whole referenced object, and that can flip too.
37
- #
38
- class Reference
39
- attr_reader :from, :to # A "from" instance is related to one "to" instance
40
- attr_reader :from_role, :to_role # For objectified facts, one role will be nil (a phantom)
41
- attr_reader :fact_type
42
- attr_accessor :fk_jump # True if this reference links a table to another in an FK (between absorbed references)
43
-
44
- # A Reference is created from a object_type in regard to a role it plays
45
- def initialize(from, role)
46
- @fk_jump = false
47
- @from = from
48
- return unless role # All done if it's a self-value reference for a ValueType
49
- @fact_type = role.fact_type
50
- if @fact_type.all_role.size == 1
51
- # @from_role is nil for a unary
52
- @to_role = role
53
- @to = role.fact_type.entity_type # nil unless the unary is objectified
54
- elsif (role.fact_type.entity_type == @from) # role is in "from", an objectified fact type
55
- @from_role = nil # Phantom role
56
- @to_role = role
57
- @to = @to_role.object_type
58
- else
59
- @from_role = role
60
- @to = role.fact_type.entity_type # If set, to_role is a phantom
61
- unless @to
62
- raise "Illegal reference through >binary fact type" if @fact_type.all_role.size >2
63
- @to_role = (role.fact_type.all_role-[role])[0]
64
- @to = @to_role.object_type
65
- end
66
- end
67
- end
68
-
69
- # What type of Role did this Reference arise from?
70
- def role_type
71
- role = @from_role||@to_role
72
- role && role.role_type
73
- end
74
-
75
- # Is this Reference covered by a mandatory constraint (implicitly or explicitly)
76
- def is_mandatory
77
- !is_unary &&
78
- (!@from_role || # All phantom roles of fact types are mandatory
79
- @from_role.is_mandatory)
80
- end
81
-
82
- # Is this Reference from a unary Role?
83
- def is_unary
84
- @to_role && @to_role.fact_type.all_role.size == 1
85
- end
86
-
87
- # If this Reference is to an objectified FactType, there is no *to_role*
88
- def is_to_objectified_fact
89
- # This case is the only one that cannot be used in the preferred identifier of @from
90
- @to && !@to_role && @from_role
91
- end
92
-
93
- # If this Reference is from an objectified FactType, there is no *from_role*
94
- def is_from_objectified_fact
95
- @to && !@from_role && @to_role
96
- end
97
-
98
- # Is this reference an injected role as a result a ValueType being a table?
99
- def is_self_value
100
- !@to && !@to_role
101
- end
102
-
103
- # Is the *to* object_type fully absorbed through this reference?
104
- def is_absorbing
105
- @to && @to.absorbed_via == self
106
- end
107
-
108
- # Is this a simple reference?
109
- def is_simple_reference
110
- # It's a simple reference to a thing if that thing is a table,
111
- # or is fully absorbed into another table but not via this reference.
112
- @to && (@to.is_table or @to.absorbed_via && !is_absorbing)
113
- end
114
-
115
- # Return the array of names for the (perhaps implicit) *to_role* of this Reference
116
- def to_names(is_prefix = true)
117
- case
118
- when is_unary
119
- if @to && @to.fact_type && is_prefix
120
- @to.name.camelwords
121
- else
122
- @to_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
123
- end
124
- when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
125
- @to.name.camelwords
126
- when !@to_role # Self-value role of an independent ValueType
127
- @from.name.camelwords + ["Value"]
128
- when @to_role.role_name # Named role
129
- @to_role.role_name.camelwords
130
- else # Use the name from the preferred reading
131
- role_ref = @to_role.preferred_reference
132
- [role_ref.leading_adjective, @to_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
133
- end
134
- end
135
-
136
- # Return the array of names for the (perhaps implicit) *from_role* of this Reference
137
- def from_names
138
- case
139
- when @from && !@from_role # @from is an objectified fact type so @from_role is a phantom
140
- @from.name.camelwords
141
- when is_unary
142
- if @from && @from.fact_type
143
- @from.name.camelwords
144
- else
145
- @from_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
146
- end
147
- when !@from_role # Self-value role of an independent ValueType
148
- @from.name.camelwords + ["Value"]
149
- when @from_role.role_name # Named role
150
- @from_role.role_name.camelwords
151
- else # Use the name from the preferred reading
152
- role_ref = @from_role.preferred_reference
153
- [role_ref.leading_adjective, @from_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
154
- end
155
- end
156
-
157
- def is_one_to_one
158
- [:one_one, :subtype, :supertype].include?(role_type)
159
- end
160
-
161
- # For a one-to-one (or a subtyping fact type), reverse the direction.
162
- def flip #:nodoc:
163
- raise "Illegal flip of #{self}" unless @to and is_one_to_one
164
-
165
- detabulate
166
- mirror
167
- tabulate
168
- end
169
-
170
- # Create a (non-tabulated) flipped version of this Reference. Careful not to tabulate it!
171
- def mirror
172
- if @to.absorbed_via == self
173
- @to.absorbed_via = nil
174
- @from.absorbed_via = self
175
- end
176
-
177
- # Flip the reference
178
- @to, @from = @from, @to
179
- @to_role, @from_role = @from_role, @to_role
180
- self
181
- end
182
-
183
- def reversed
184
- clone.mirror
185
- end
186
-
187
- def tabulate #:nodoc:
188
- # Add to @to and @from's reference lists
189
- @from.references_from << self
190
- @to.references_to << self if @to # Guard against self-values
191
-
192
- trace :references, "Adding #{to_s}"
193
- self
194
- end
195
-
196
- def detabulate #:nodoc:
197
- # Remove from @to and @from's reference lists if present
198
- return unless @from.references_from.delete(self)
199
- @to.references_to.delete self if @to # Guard against self-values
200
- trace :references, "Dropping #{to_s}"
201
- self
202
- end
203
-
204
- def to_s #:nodoc:
205
- ref_type = fk_jump ? "jumping to" : (is_absorbing ? "absorbing" : "to")
206
- "reference from #{@from.name}#{@to ? " #{ref_type} #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "")
207
- end
208
-
209
- # The reading for the fact type underlying this Reference
210
- def reading
211
- is_self_value ? "#{from.name} has value" : @fact_type.reading_preferably_starting_with_role(@from_role).expand
212
- end
213
-
214
- def verbalised_path reverse = false
215
- return "#{from.name} Value" if is_self_value
216
- objectified = fact_type.entity_type
217
- f = # Switch to the Link Fact Type if we're traversing an objectification
218
- (to_role && to_role.link_fact_type) ||
219
- (from_role && from_role.link_fact_type) ||
220
- fact_type
221
-
222
- start_role =
223
- if objectified
224
- target = reverse ? to : from
225
- [to_role, from_role, f.all_role[0]].compact.detect{|role| role.object_type == target}
226
- else
227
- reverse ? to_role : from_role
228
- end
229
- reading = f.reading_preferably_starting_with_role(start_role)
230
- (is_mandatory || is_unary ? '' : 'maybe ') +
231
- reading.expand
232
- end
233
-
234
- def inspect #:nodoc:
235
- to_s
236
- end
237
- end
238
- end
239
-
240
- module Metamodel #:nodoc:
241
- class ObjectType
242
- # Say whether the independence of this object is still under consideration
243
- # This is used in detecting dependency cycles, such as occurs in the Metamodel
244
- attr_accessor :tentative #:nodoc:
245
- attr_writer :is_table # The two ObjectType subclasses provide the attr_reader method
246
-
247
- def show_tabular #:nodoc:
248
- (tentative ? "tentatively " : "") +
249
- (is_table ? "" : "not ")+"a table"
250
- end
251
-
252
- def definitely_table #:nodoc:
253
- @is_table = true
254
- @tentative = false
255
- end
256
-
257
- def definitely_not_table #:nodoc:
258
- @is_table = false
259
- @tentative = false
260
- end
261
-
262
- def probably_table #:nodoc:
263
- @is_table = true
264
- @tentative = true
265
- end
266
-
267
- def probably_not_table #:nodoc:
268
- @is_table = false
269
- @tentative = true
270
- end
271
-
272
- # References from this ObjectType
273
- def references_from
274
- @references_from ||= []
275
- end
276
-
277
- # References to this ObjectType
278
- def references_to
279
- @references_to ||= []
280
- end
281
-
282
- # True if this ObjectType has any References (to or from)
283
- def has_references #:nodoc:
284
- @references_from || @references_to
285
- end
286
-
287
- def clear_references #:nodoc:
288
- # Clear any previous references:
289
- @references_to = nil
290
- @references_from = nil
291
- end
292
-
293
- def populate_references #:nodoc:
294
- all_role.each do |role|
295
- # It's possible that this role is in an implicit or derived fact type. Skip it if so.
296
- next if role.fact_type.is_a?(LinkFactType) or
297
- # REVISIT: dafuq? Is this looking for a constraint over a derivation? This looks wrong.
298
- role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].play or
299
- # This is not yet actually set, and wouldn't handle constraint derivations anyhow:
300
- role.variable_as_projection
301
-
302
- populate_reference role
303
- end
304
- end
305
-
306
- def populate_reference role #:nodoc:
307
- role_type = role.role_type
308
- trace :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'"
309
- case role_type
310
- when :many_one
311
- ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference
312
-
313
- when :one_many
314
- if role.fact_type.entity_type == self # A Role of this objectified FactType
315
- ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference; check that
316
- else
317
- # Can't absorb many of these into one of those
318
- #trace :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}"
319
- end
320
-
321
- when :unary
322
- ActiveFacts::Persistence::Reference.new(self, role).tabulate # A simple reference
323
-
324
- when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned
325
- # REVISIT: Or when partitioned
326
- raise "Internal error, expected TypeInheritance" unless role.fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance)
327
- counterpart_role = (role.fact_type.all_role.to_a-[role])[0]
328
- if role.fact_type.assimilation or counterpart_role.object_type.is_separate
329
- trace :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
330
- else
331
- r = ActiveFacts::Persistence::Reference.new(self, role)
332
- r.to.absorbed_via = r
333
- trace :references, "supertype #{name} absorbs subtype #{r.to.name}"
334
- r.tabulate
335
- end
336
-
337
- when :subtype # This object is a supertype, which can absorb the subtype unless that's independent
338
- if role.fact_type.assimilation or is_separate
339
- ActiveFacts::Persistence::Reference.new(self, role).tabulate
340
- # If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
341
- else
342
- # trace :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}"
343
- end
344
-
345
- when :one_one
346
- r = ActiveFacts::Persistence::Reference.new(self, role)
347
-
348
- # Decide which way the one-to-one is likely to go; it will be flipped later if necessary.
349
- # Force the decision if just one is independent:
350
- # REVISIT: Decide whether supertype assimilation can affect this
351
- r.tabulate and return if is_separate and !r.to.is_separate
352
- return if !is_separate and r.to.is_separate
353
-
354
- if is_a?(ValueType)
355
- # Never absorb an entity type into a value type
356
- return if r.to.is_a?(EntityType) # Don't tabulate it
357
- else
358
- if r.to.is_a?(ValueType)
359
- r.tabulate # Always absorb a value type into an entity type
360
- return
361
- end
362
-
363
- # Force the decision if one EntityType identifies another:
364
- if preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == r.to_role}
365
- trace :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere"
366
- return
367
- end
368
- if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role}
369
- trace :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
370
- r.to.absorbed_via = r
371
- # We can't be absorbed into our supertype!
372
- # REVISIT: We might need to flip all one-to-ones as well
373
- r.to.references_to.clone.map{|q|q.flip if q.to_role.role_type == :subtype }
374
- r.tabulate
375
- return
376
- end
377
- end
378
-
379
- # Either both EntityTypes, or both ValueTypes.
380
- # Make an arbitrary (but stable) decision which way to go. We might flip it later,
381
- # but not frivolously; the Ruby API column name generation duplicates this logic.
382
- unless r.from.name.downcase < r.to.name.downcase or
383
- (r.from == r.to && references_to.detect{|ref| ref.to_role == role}) # one-to-one self reference, done already
384
- r.tabulate
385
- end
386
- else
387
- # REVISIT: Should we implicitly objectify this fact type here and add a spanning UC?
388
- raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
389
- end
390
- end
391
- end
392
-
393
- class EntityType < DomainObjectType
394
- def populate_references #:nodoc:
395
- if fact_type && fact_type.all_role.size > 1
396
- # NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead:
397
- fact_type.preferred_reading.role_sequence.all_role_ref.map(&:role).each do |role|
398
- populate_reference role # Objectified fact role, handled specially
399
- end
400
- end
401
- super
402
- end
403
- end
404
-
405
- class Vocabulary
406
- def populate_all_references #:nodoc:
407
- trace :references, "Populating all object_type references" do
408
- all_object_type.each do |object_type|
409
- trace :references, "Populating references for #{object_type.name}" do
410
- object_type.populate_references
411
- end
412
- end
413
- end
414
- show_all_references
415
- end
416
-
417
- def show_all_references
418
- if trace :references
419
- trace :references, "Finished object_type references" do
420
- all_object_type.each do |object_type|
421
- next unless object_type.references_from.size > 0
422
- trace :references, "#{object_type.name}:" do
423
- object_type.references_from.each do |ref|
424
- trace :references, "#{ref}"
425
- end
426
- end
427
- end
428
- end
429
- end
430
- end
431
- end
432
-
433
- end
434
- end
@@ -1,380 +0,0 @@
1
- #
2
- # ActiveFacts Relational mapping and persistence.
3
- # Tables; Calculate the relational composition of a given Vocabulary.
4
- # The composition consists of decisions about which ObjectTypes are tables,
5
- # and what columns (absorbed roled) those tables will have.
6
- #
7
- # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
8
- #
9
- # This module has the following known problems:
10
- #
11
- # * When a subtype has no mandatory roles, we should support an optional schema transformation step
12
- # that introduces a boolean (is_subtype) to indicate it's that subtype.
13
- #
14
-
15
- require 'activefacts/persistence/reference'
16
-
17
- module ActiveFacts
18
- module Metamodel
19
-
20
- class ValueType < DomainObjectType
21
- def absorbed_via #:nodoc:
22
- # ValueTypes aren't absorbed in the way EntityTypes are
23
- nil
24
- end
25
-
26
- # Returns true if this ValueType is a table
27
- def is_table
28
- return @is_table if @is_table != nil
29
-
30
- # Always a table if marked so:
31
- if is_separate
32
- trace :absorption, "ValueType #{name} is declared independent or separate"
33
- @tentative = false
34
- return @is_table = true
35
- end
36
-
37
- # Only a table if it has references (to another ValueType)
38
- if !references_from.empty? && !is_auto_assigned
39
- trace :absorption, "#{name} is a table because it has #{references_from.size} references to it"
40
- @is_table = true
41
- else
42
- @is_table = false
43
- end
44
- @tentative = false
45
-
46
- @is_table
47
- end
48
-
49
- # Is this ValueType auto-assigned either at assert or on first save to the database?
50
- def is_auto_assigned
51
- type = self
52
- while type
53
- return true if type.name =~ /^Auto/ || type.transaction_phase
54
- type = type.supertype
55
- end
56
- end
57
- false
58
- end
59
-
60
- class EntityType < DomainObjectType
61
- # A Reference from an entity type that fully absorbs this one
62
- attr_accessor :absorbed_via #:nodoc:
63
- attr_accessor :absorbed_mirror #:nodoc:
64
-
65
- def is_auto_assigned #:nodoc:
66
- false
67
- end
68
-
69
- # Returns true if this EntityType is a table
70
- def is_table
71
- return @is_table if @is_table != nil # We already make a guess or decision
72
-
73
- @tentative = false
74
-
75
- # Always a table if marked so
76
- if is_separate
77
- trace :absorption, "EntityType #{name} is declared independent or separate"
78
- return @is_table = true
79
- end
80
-
81
- # Always a table if nowhere else to go, and has no one-to-ones that might flip:
82
- if references_to.empty? and
83
- !references_from.detect{|ref| ref.role_type == :one_one }
84
- trace :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
85
- return @is_table = true
86
- end
87
-
88
- # Subtypes may be partitioned or separate, in which case they're definitely tables.
89
- # Otherwise, if their identification is inherited from a supertype, they're definitely absorbed.
90
- # If theey have separate identification, it might absorb them.
91
- if (!supertypes.empty?)
92
- as_ti = all_supertype_inheritance.detect{|ti| ti.assimilation}
93
- @is_table = as_ti != nil
94
- if @is_table
95
- trace :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
96
- else
97
- identifying_fact_type = preferred_identifier.role_sequence.all_role_ref.to_a[0].role.fact_type
98
- if identifying_fact_type.is_a?(TypeInheritance)
99
- trace :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
100
- @is_table = false
101
- else
102
- # Possibly absorbed, we'll have to see how that pans out
103
- @tentative = true
104
- end
105
- end
106
- return @is_table
107
- end
108
-
109
- # If the preferred_identifier includes an auto_assigned ValueType
110
- # and this object is absorbed in more than one place, we need a table
111
- # to manage the auto-assignment.
112
- if references_to.size > 1 and
113
- preferred_identifier.role_sequence.all_role_ref.detect {|rr|
114
- next false unless rr.role.object_type.is_a? ValueType
115
- rr.role.object_type.is_auto_assigned
116
- }
117
- trace :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
118
- @tentative = false
119
- return @is_table = true
120
- end
121
-
122
- @tentative = true
123
- @is_table = true
124
- end
125
- end # EntityType class
126
-
127
- class Role #:nodoc:
128
- def role_type
129
- # TypeInheritance roles are always 1:1
130
- if TypeInheritance === fact_type
131
- return object_type == fact_type.supertype ? :supertype : :subtype
132
- end
133
-
134
- # Always N:1 if unary:
135
- return :unary if fact_type.all_role.size == 1
136
-
137
- # List the UCs on this fact type:
138
- all_uniqueness_constraints =
139
- fact_type.all_role.map do |fact_role|
140
- fact_role.all_role_ref.map do |rr|
141
- rr.role_sequence.all_presence_constraint.select do |pc|
142
- pc.max_frequency == 1
143
- end
144
- end
145
- end.flatten.uniq
146
-
147
- to_1 =
148
- all_uniqueness_constraints.
149
- detect do |c|
150
- (rr = c.role_sequence.all_role_ref.single) and
151
- rr.role == self
152
- end
153
- # REVISIT: check mapping pragmas, e.g. by to_1.concept.all_concept_annotation.detect{|ca| ca.mapping_annotation == 'separate'}
154
-
155
- if fact_type.entity_type
156
- # This is a role in an objectified fact type
157
- from_1 = true
158
- else
159
- # It's to-1 if a UC exists over roles of this FT that doesn't cover this role:
160
- from_1 = all_uniqueness_constraints.detect{|uc|
161
- !uc.role_sequence.all_role_ref.detect{|rr| rr.role == self || rr.role.fact_type != fact_type}
162
- }
163
- end
164
-
165
- if from_1
166
- return to_1 ? :one_one : :one_many
167
- else
168
- return to_1 ? :many_one : :many_many
169
- end
170
- end
171
-
172
- end
173
-
174
- class Vocabulary
175
- @@relational_transforms = []
176
-
177
- # return an Array of ObjectTypes that will have their own tables
178
- def tables
179
- decide_tables if !@tables
180
- @@relational_transforms.each{|tr| tr.call(self)}
181
- @tables
182
- end
183
-
184
- def self.relational_transform &block
185
- # Add this block to the additional transformations which will be applied
186
- # to the relational schema after the initial absorption.
187
- # For example, to perform injection of surrogate keys to replace composite keys...
188
- @@relational_transforms << block
189
- end
190
-
191
- def wipe_existing_mapping
192
- all_object_type.each do |object_type|
193
- object_type.clear_references
194
- object_type.wipe_columns
195
- object_type.is_table = nil # Undecided; force an attempt to decide
196
- object_type.tentative = true # Uncertain
197
- end
198
- end
199
-
200
- def decide_tables #:nodoc:
201
- # Strategy:
202
- # 1) Populate references for all ObjectTypes
203
- # 2) Decide which ObjectTypes must be and must not be tables
204
- # a. ObjectTypes labelled is_independent/separate are tables (See the is_table methods above)
205
- # b. Entity types having no references to them must be tables
206
- # c. subtypes are not tables unless marked with assimilation = separate or partitioned
207
- # d. ValueTypes are never tables unless they independent or can have references (to other ValueTypes)
208
- # e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference
209
- # f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed
210
- # g. An EntityType that must has references other than its PI must be a table (unless it has exactly one reference to it)
211
- # h. supertypes are elided if all roles are absorbed into subtypes:
212
- # - partitioned subtype exhaustion
213
- # - subtype extension where supertype has only PI roles and no AutoInc
214
- # 3) any ValueType that has references from it must become a table if not already
215
-
216
- wipe_existing_mapping
217
-
218
- populate_all_references
219
-
220
- trace :absorption, "Calculating relational composition" do
221
- # Evaluate the possible independence of each object_type, building an array of object_types of indeterminate status:
222
- undecided =
223
- all_object_type.select do |object_type|
224
- object_type.is_table # Ask it whether it thinks it should be a table
225
- object_type.tentative # Selection criterion
226
- end
227
-
228
- if trace :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
229
- (all_object_type-undecided).each {|object_type|
230
- next if ValueType === object_type && !object_type.is_table # Skip unremarkable cases
231
- trace :absorption do
232
- trace :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
233
- end
234
- }
235
- end
236
-
237
- pass = 0
238
- begin # Loop while we continue to make progress
239
- pass += 1
240
- trace :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
241
- possible_flips = {} # A hash by table containing an array of references that can be flipped
242
- finalised = # Make an array of things we finalised during this pass
243
- undecided.select do |object_type|
244
- trace :absorption, "Considering #{object_type.name}:" do
245
- trace :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
246
- trace :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
247
-
248
- # Always absorb an objectified unary into its role player:
249
- if object_type.fact_type && object_type.fact_type.all_role.size == 1
250
- trace :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
251
- object_type.definitely_not_table
252
- next object_type
253
- end
254
-
255
- # If the PI contains one role only, played by an entity type that can absorb us, do that.
256
- pi_roles = object_type.preferred_identifier.role_sequence.all_role_ref.map(&:role)
257
- trace :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
258
- first_pi_role = pi_roles[0]
259
- pi_ref = nil
260
- if pi_roles.size == 1 and
261
- object_type.references_to.detect do |ref|
262
- if ref.from_role == first_pi_role and ref.from.is_a?(EntityType) # and ref.is_mandatory # REVISIT
263
- pi_ref = ref
264
- end
265
- end
266
-
267
- trace :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
268
- object_type.definitely_not_table
269
- next object_type
270
- end
271
-
272
- # If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
273
- non_identifying_refs_from =
274
- object_type.references_from.reject{|ref|
275
- pi_roles.include?(ref.to_role)
276
- }
277
- trace :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
278
-
279
- =begin
280
- # This is kinda arbitrary. We need a policy for evaluating optional flips, so we can decide if they "improve" things.
281
- # The flipping that occurs below always eliminates a table by absorption, but this doesn't.
282
-
283
- # If all non-identifying functional roles are one-to-ones that can be flipped, do that:
284
- if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) }
285
- trace :absorption, "Flipping references from #{object_type.name}" do
286
- non_identifying_refs_from.each do |ref|
287
- trace :absorption, "Flipping #{ref}"
288
- ref.flip
289
- end
290
- end
291
- non_identifying_refs_from = []
292
- end
293
- =end
294
-
295
- if object_type.references_to.size > 1 and
296
- non_identifying_refs_from.size > 0
297
- trace :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
298
- object_type.definitely_table
299
- next object_type
300
- end
301
-
302
- absorption_paths =
303
- (
304
- non_identifying_refs_from.reject do |ref|
305
- !ref.to or ref.to.absorbed_via == ref
306
- end+object_type.references_to
307
- ).reject do |ref|
308
- next true if !ref.to.is_table or !ref.is_one_to_one
309
-
310
- # Don't absorb an object along a non-mandatory role (otherwise if it doesn't play that role, it can't exist either)
311
- from_is_mandatory = !!ref.is_mandatory
312
- to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
313
-
314
- bad = !(ref.from == object_type ? from_is_mandatory : to_is_mandatory)
315
- trace :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
316
- bad
317
- end
318
-
319
- # If this object can be fully absorbed, do that (might require flipping some references)
320
- if absorption_paths.size > 0
321
- trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
322
- absorption_paths.each do |ref|
323
- trace :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
324
- ref.flip if object_type == ref.from
325
- end
326
- object_type.definitely_not_table
327
- next object_type
328
- end
329
-
330
- if non_identifying_refs_from.size == 0
331
- # REVISIT: This allows absorption along a non-mandatory role of a objectified fact type
332
- # and object_type.references_to.all?{|ref| ref.is_mandatory }
333
- # and (!object_type.is_a?(EntityType) ||
334
- # # REVISIT: The roles may be collectively but not individually mandatory.
335
- # object_type.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
336
- trace :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
337
- object_type.definitely_not_table
338
- next object_type
339
- end
340
-
341
- false # Failed to decide about this entity_type this time around
342
- end
343
- end
344
-
345
- undecided -= finalised
346
- trace :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
347
- end while !finalised.empty?
348
-
349
- # A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
350
- # unless it should absorb something else (another ValueType is all it could be):
351
- all_object_type.each do |object_type|
352
- if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0)
353
- if !object_type.references_from.detect{|r| !r.is_one_to_one || !r.to.is_table}
354
- trace :absorption, "Flipping references from #{object_type.name}; they're all to tables"
355
- object_type.references_from.map(&:flip)
356
- else
357
- trace :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
358
- object_type.probably_table
359
- end
360
- end
361
- end
362
-
363
- # Now, evaluate all possibilities of the tentative assignments
364
- # Incomplete. Apparently unnecessary as well... so far. We'll see.
365
- if trace :absorption
366
- undecided.each do |object_type|
367
- trace :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
368
- end
369
- end
370
- end
371
-
372
- @tables =
373
- all_object_type.
374
- select { |f| f.is_table }.
375
- sort_by { |table| table.name }
376
- end
377
- end
378
-
379
- end
380
- end