activefacts 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Manifest.txt +28 -33
- data/Rakefile +11 -12
- data/bin/cql +90 -46
- data/examples/CQL/Blog.cql +2 -1
- data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
- data/examples/CQL/Death.cql +1 -1
- data/examples/CQL/Diplomacy.cql +9 -9
- data/examples/CQL/Genealogy.cql +3 -2
- data/examples/CQL/Insurance.cql +10 -7
- data/examples/CQL/JoinEquality.cql +2 -2
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +73 -53
- data/examples/CQL/MetamodelNext.cql +89 -67
- data/examples/CQL/OneToOnes.cql +2 -2
- data/examples/CQL/ServiceDirector.cql +10 -5
- data/examples/CQL/Supervision.cql +3 -3
- data/examples/CQL/Tests.Test5.Load.cql +1 -1
- data/examples/CQL/Warehousing.cql +4 -2
- data/lib/activefacts/cql/CQLParser.treetop +26 -60
- data/lib/activefacts/cql/Context.treetop +12 -2
- data/lib/activefacts/cql/Expressions.treetop +14 -30
- data/lib/activefacts/cql/FactTypes.treetop +165 -110
- data/lib/activefacts/cql/Language/English.treetop +167 -54
- data/lib/activefacts/cql/LexicalRules.treetop +16 -2
- data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
- data/lib/activefacts/cql/Terms.treetop +57 -27
- data/lib/activefacts/cql/ValueTypes.treetop +39 -13
- data/lib/activefacts/cql/compiler.rb +5 -3
- data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
- data/lib/activefacts/cql/compiler/constraint.rb +178 -275
- data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
- data/lib/activefacts/cql/compiler/expression.rb +418 -0
- data/lib/activefacts/cql/compiler/fact.rb +146 -145
- data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
- data/lib/activefacts/cql/compiler/join.rb +159 -0
- data/lib/activefacts/cql/compiler/shared.rb +51 -23
- data/lib/activefacts/cql/compiler/value_type.rb +56 -2
- data/lib/activefacts/cql/parser.rb +15 -4
- data/lib/activefacts/generate/absorption.rb +7 -7
- data/lib/activefacts/generate/cql.rb +100 -37
- data/lib/activefacts/generate/oo.rb +28 -51
- data/lib/activefacts/generate/ordered.rb +60 -36
- data/lib/activefacts/generate/ruby.rb +6 -6
- data/lib/activefacts/generate/sql/server.rb +4 -4
- data/lib/activefacts/input/orm.rb +71 -53
- data/lib/activefacts/persistence.rb +1 -1
- data/lib/activefacts/persistence/columns.rb +27 -23
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +17 -17
- data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
- data/lib/activefacts/persistence/reference.rb +61 -36
- data/lib/activefacts/persistence/tables.rb +61 -59
- data/lib/activefacts/support.rb +54 -29
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +99 -54
- data/lib/activefacts/vocabulary/metamodel.rb +43 -37
- data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
- data/spec/absorption_spec.rb +8 -8
- data/spec/cql/comparison_spec.rb +91 -0
- data/spec/cql/contractions_spec.rb +251 -0
- data/spec/cql/entity_type_spec.rb +319 -0
- data/spec/cql/expressions_spec.rb +63 -0
- data/spec/cql/fact_type_matching_spec.rb +283 -0
- data/spec/cql/french_spec.rb +21 -0
- data/spec/cql/parser/bad_literals_spec.rb +86 -0
- data/spec/cql/parser/constraints_spec.rb +19 -0
- data/spec/cql/parser/entity_types_spec.rb +106 -0
- data/spec/cql/parser/expressions_spec.rb +179 -0
- data/spec/cql/parser/fact_types_spec.rb +41 -0
- data/spec/cql/parser/literals_spec.rb +312 -0
- data/spec/cql/parser/pragmas_spec.rb +89 -0
- data/spec/cql/parser/value_types_spec.rb +42 -0
- data/spec/cql/role_matching_spec.rb +147 -0
- data/spec/cql/samples_spec.rb +9 -9
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/cql_dm_spec.rb +116 -0
- data/spec/cql_mysql_spec.rb +1 -1
- data/spec/cql_ruby_spec.rb +1 -1
- data/spec/cql_sql_spec.rb +3 -3
- data/spec/cql_symbol_tables_spec.rb +30 -30
- data/spec/cqldump_spec.rb +4 -4
- data/spec/helpers/array_matcher.rb +32 -27
- data/spec/helpers/diff_matcher.rb +6 -26
- data/spec/helpers/file_matcher.rb +41 -32
- data/spec/helpers/parse_to_ast_matcher.rb +76 -0
- data/spec/helpers/string_matcher.rb +32 -31
- data/spec/norma_cql_spec.rb +1 -1
- data/spec/norma_ruby_spec.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +1 -1
- data/spec/norma_sql_spec.rb +3 -1
- data/spec/norma_tables_spec.rb +1 -1
- data/spec/ruby_api_spec.rb +23 -0
- data/spec/spec_helper.rb +5 -4
- metadata +66 -66
- data/examples/CQL/OrienteeringER.cql +0 -58
- data/lib/activefacts/api.rb +0 -44
- data/lib/activefacts/api/concept.rb +0 -410
- data/lib/activefacts/api/constellation.rb +0 -128
- data/lib/activefacts/api/entity.rb +0 -256
- data/lib/activefacts/api/instance.rb +0 -60
- data/lib/activefacts/api/instance_index.rb +0 -80
- data/lib/activefacts/api/numeric.rb +0 -167
- data/lib/activefacts/api/role.rb +0 -80
- data/lib/activefacts/api/role_proxy.rb +0 -70
- data/lib/activefacts/api/role_values.rb +0 -117
- data/lib/activefacts/api/standard_types.rb +0 -87
- data/lib/activefacts/api/support.rb +0 -65
- data/lib/activefacts/api/value.rb +0 -135
- data/lib/activefacts/api/vocabulary.rb +0 -82
- data/spec/api/autocounter.rb +0 -82
- data/spec/api/constellation.rb +0 -130
- data/spec/api/entity_type.rb +0 -103
- data/spec/api/instance.rb +0 -461
- data/spec/api/roles.rb +0 -124
- data/spec/api/value_type.rb +0 -112
- data/spec/api_spec.rb +0 -13
- data/spec/cql/matching_spec.rb +0 -517
- data/spec/cql/unit_spec.rb +0 -394
- data/spec/spec.opts +0 -1
@@ -1,20 +1,20 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Relational mapping and persistence.
|
3
|
-
# A ForeignKey exists for every Reference from a
|
3
|
+
# A ForeignKey exists for every Reference from a ObjectType to another ObjectType that's a table.
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
module ActiveFacts
|
8
8
|
module Persistence
|
9
9
|
class ForeignKey
|
10
|
-
# What table (
|
10
|
+
# What table (ObjectType) is the FK from?
|
11
11
|
def from; @from; end
|
12
12
|
|
13
|
-
# What table (
|
13
|
+
# What table (ObjectType) is the FK to?
|
14
14
|
def to; @to; end
|
15
15
|
|
16
16
|
# What reference created the FK?
|
17
|
-
def reference; @
|
17
|
+
def reference; @fk_ref; end
|
18
18
|
|
19
19
|
# What columns in the *from* table form the FK
|
20
20
|
def from_columns; @from_columns; end
|
@@ -30,7 +30,7 @@ module ActiveFacts
|
|
30
30
|
end
|
31
31
|
|
32
32
|
module Metamodel #:nodoc:
|
33
|
-
class
|
33
|
+
class ObjectType
|
34
34
|
# When an EntityType is fully absorbed, its foreign keys are too.
|
35
35
|
# Return an Array of Reference paths for such absorbed FKs
|
36
36
|
def all_absorbed_foreign_key_reference_path
|
@@ -61,7 +61,7 @@ module ActiveFacts
|
|
61
61
|
fk_ref_paths.map do |fk_ref_path|
|
62
62
|
debug :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
|
63
63
|
|
64
|
-
from_columns = columns.select{|column|
|
64
|
+
from_columns = (columns||all_columns({})).select{|column|
|
65
65
|
column.references[0...fk_ref_path.size] == fk_ref_path
|
66
66
|
}
|
67
67
|
debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Relational mapping and persistence.
|
3
|
-
# An Index on a
|
4
|
-
# into that
|
3
|
+
# An Index on a ObjectType is used to represent a unique constraint across roles absorbed
|
4
|
+
# into that object_type's table.
|
5
5
|
#
|
6
6
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
7
7
|
#
|
@@ -16,7 +16,7 @@ module ActiveFacts
|
|
16
16
|
def on; @on; end
|
17
17
|
|
18
18
|
# If a non-mandatory reference was absorbed, only the non-nil instances are unique.
|
19
|
-
# Return the
|
19
|
+
# Return the ObjectType that was absorbed, which might differ from this Index's table.
|
20
20
|
def over; @over; end
|
21
21
|
|
22
22
|
# Return the array of columns in this index
|
@@ -78,7 +78,7 @@ module ActiveFacts
|
|
78
78
|
end
|
79
79
|
|
80
80
|
module Metamodel #:nodoc:
|
81
|
-
class
|
81
|
+
class ObjectType
|
82
82
|
# An array of each Index for this table
|
83
83
|
def indices; @indices; end
|
84
84
|
|
@@ -181,23 +181,23 @@ module ActiveFacts
|
|
181
181
|
|
182
182
|
class Vocabulary
|
183
183
|
def populate_all_indices #:nodoc:
|
184
|
-
debug :index, "Populating all
|
185
|
-
|
186
|
-
|
184
|
+
debug :index, "Populating all object_type indices" do
|
185
|
+
all_object_type.each do |object_type|
|
186
|
+
object_type.clear_indices
|
187
187
|
end
|
188
|
-
|
189
|
-
next unless
|
190
|
-
debug :index, "Populating indices for #{
|
191
|
-
|
188
|
+
all_object_type.each do |object_type|
|
189
|
+
next unless object_type.is_table
|
190
|
+
debug :index, "Populating indices for #{object_type.name}" do
|
191
|
+
object_type.populate_indices
|
192
192
|
end
|
193
193
|
end
|
194
194
|
end
|
195
|
-
debug :index, "Finished
|
196
|
-
|
197
|
-
next unless
|
198
|
-
next unless
|
199
|
-
debug :index, "#{
|
200
|
-
|
195
|
+
debug :index, "Finished object_type indices" do
|
196
|
+
all_object_type.each do |object_type|
|
197
|
+
next unless object_type.is_table
|
198
|
+
next unless object_type.indices.size > 0
|
199
|
+
debug :index, "#{object_type.name}:" do
|
200
|
+
object_type.indices.each do |index|
|
201
201
|
debug :index, index
|
202
202
|
end
|
203
203
|
end
|
@@ -2,7 +2,7 @@ require 'activefacts/support'
|
|
2
2
|
|
3
3
|
module ActiveFacts
|
4
4
|
module API
|
5
|
-
module
|
5
|
+
module ObjectType
|
6
6
|
def table
|
7
7
|
@is_table = true
|
8
8
|
end
|
@@ -29,7 +29,7 @@ module ActiveFacts
|
|
29
29
|
end.inject([]) do |columns, role|
|
30
30
|
rn = role.name.to_s.split(/_/)
|
31
31
|
debug :persistence, "Role #{rn*'.'}" do
|
32
|
-
columns += role.
|
32
|
+
columns += role.counterpart_object_type.__absorb([rn], role.counterpart)
|
33
33
|
end
|
34
34
|
end +
|
35
35
|
# And finally all absorbed subtypes:
|
@@ -65,11 +65,11 @@ module ActiveFacts
|
|
65
65
|
if (role = fully_absorbed) && role != except_role
|
66
66
|
# If this non-table is fully absorbed into another table (not our caller!)
|
67
67
|
# (another table plays its single identifying role), then absorb that role only.
|
68
|
-
#
|
68
|
+
# counterpart_object_type = role.counterpart_object_type
|
69
69
|
# This omission matches the one in columns.rb, see EntityType#reference_columns
|
70
70
|
# new_prefix = prefix + [role.name.to_s.split(/_/)]
|
71
71
|
debug :persistence, "Reference to #{role.name} (absorbed elsewhere)" do
|
72
|
-
role.
|
72
|
+
role.counterpart_object_type.__absorb(prefix, role.counterpart)
|
73
73
|
end
|
74
74
|
else
|
75
75
|
# Not a table -> all roles are absorbed
|
@@ -125,7 +125,7 @@ module ActiveFacts
|
|
125
125
|
debug :persistence, "truncating transitive identifying role #{n.inspect}"
|
126
126
|
owner.size.times { n.shift }
|
127
127
|
new_prefix = prefix + [n]
|
128
|
-
elsif (c = role.
|
128
|
+
elsif (c = role.counterpart_object_type).is_entity_type and
|
129
129
|
(irn = c.identifying_role_names).size == 1 and
|
130
130
|
#irn[0].to_s.split(/_/)[0] == role.owner.basename.downcase
|
131
131
|
irn[0] == role.counterpart.name
|
@@ -139,7 +139,7 @@ module ActiveFacts
|
|
139
139
|
#debug :persistence, "new_prefix is #{new_prefix*"."}"
|
140
140
|
|
141
141
|
debug :persistence, "Absorbing role #{role.name} as #{new_prefix[prefix.size..-1]*"."}" do
|
142
|
-
role.
|
142
|
+
role.counterpart_object_type.__absorb(new_prefix, role.counterpart)
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
@@ -159,7 +159,7 @@ module ActiveFacts
|
|
159
159
|
def fully_absorbed
|
160
160
|
return false unless (ir = identifying_role_names) && ir.size == 1
|
161
161
|
role = roles(ir[0])
|
162
|
-
return role if ((cp = role.
|
162
|
+
return role if ((cp = role.counterpart_object_type).is_table ||
|
163
163
|
(cp.is_entity_type && cp.fully_absorbed))
|
164
164
|
return superclass if superclass.is_entity_type # Absorbed subtype
|
165
165
|
nil
|
@@ -168,10 +168,10 @@ module ActiveFacts
|
|
168
168
|
end
|
169
169
|
|
170
170
|
# A one-to-one can be absorbed into either table. We decide which by comparing
|
171
|
-
# the names, just as happens in
|
171
|
+
# the names, just as happens in ObjectType.populate_reference (see reference.rb)
|
172
172
|
class Role
|
173
173
|
def counterpart_unary_has_precedence
|
174
|
-
|
174
|
+
counterpart_object_type.is_table_subtype and
|
175
175
|
counterpart.unique and
|
176
176
|
owner.name.downcase < counterpart.owner.name.downcase
|
177
177
|
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Relational mapping and persistence.
|
3
|
-
# Reference from one
|
3
|
+
# Reference from one ObjectType to another, used to decide the relational mapping.
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
|
-
# A Reference from one
|
8
|
-
# (including subtyping), and also for a unary role (implicitly to Boolean
|
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
9
|
# A 1-1 or subtyping reference should be created in only one direction, and may be flipped
|
10
10
|
# if needed.
|
11
11
|
#
|
12
|
-
# A reference to a
|
13
|
-
# become a foreign key, otherwise it will absorb all that
|
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
14
|
#
|
15
|
-
# Reference objects update each
|
15
|
+
# Reference objects update each object_type's list of the references *to* and *from* that object_type.
|
16
16
|
#
|
17
17
|
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
18
18
|
#
|
@@ -22,9 +22,9 @@ module ActiveFacts
|
|
22
22
|
|
23
23
|
# This class contains the core data structure used in composing a relational schema.
|
24
24
|
#
|
25
|
-
# A Reference is *from* one
|
26
|
-
# When either
|
27
|
-
# When the Reference from_role is of a unary fact type, there's no to_role or to
|
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
28
|
# The final kind of Reference is a self-reference which is added to a ValueType that becomes a table.
|
29
29
|
#
|
30
30
|
# When the underlying fact type is a one-to-one (including an inheritance fact type), the Reference may be flipped.
|
@@ -40,7 +40,7 @@ module ActiveFacts
|
|
40
40
|
attr_reader :from_role, :to_role # For objectified facts, one role will be nil (a phantom)
|
41
41
|
attr_reader :fact_type
|
42
42
|
|
43
|
-
# A Reference is created from a
|
43
|
+
# A Reference is created from a object_type in regard to a role it plays
|
44
44
|
def initialize(from, role)
|
45
45
|
@from = from
|
46
46
|
return unless role # All done if it's a self-value reference for a ValueType
|
@@ -52,14 +52,14 @@ module ActiveFacts
|
|
52
52
|
elsif (role.fact_type.entity_type == @from) # role is in "from", an objectified fact type
|
53
53
|
@from_role = nil # Phantom role
|
54
54
|
@to_role = role
|
55
|
-
@to = @to_role.
|
55
|
+
@to = @to_role.object_type
|
56
56
|
else
|
57
57
|
@from_role = role
|
58
58
|
@to = role.fact_type.entity_type # If set, to_role is a phantom
|
59
59
|
unless @to
|
60
60
|
raise "Illegal reference through >binary fact type" if @fact_type.all_role.size >2
|
61
61
|
@to_role = (role.fact_type.all_role-[role])[0]
|
62
|
-
@to = @to_role.
|
62
|
+
@to = @to_role.object_type
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
@@ -98,7 +98,7 @@ module ActiveFacts
|
|
98
98
|
!@to && !@to_role
|
99
99
|
end
|
100
100
|
|
101
|
-
# Is the *to*
|
101
|
+
# Is the *to* object_type fully absorbed through this reference?
|
102
102
|
def is_absorbing
|
103
103
|
@to && @to.absorbed_via == self
|
104
104
|
end
|
@@ -127,7 +127,28 @@ module ActiveFacts
|
|
127
127
|
@to_role.role_name.camelwords
|
128
128
|
else # Use the name from the preferred reading
|
129
129
|
role_ref = @to_role.preferred_reference
|
130
|
-
[role_ref.leading_adjective, @to_role.
|
130
|
+
[role_ref.leading_adjective, @to_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return the array of names for the (perhaps implicit) *from_role* of this Reference
|
135
|
+
def from_names
|
136
|
+
case
|
137
|
+
when is_unary
|
138
|
+
if @from && @from.fact_type
|
139
|
+
@from.name.camelwords
|
140
|
+
else
|
141
|
+
@from_role.fact_type.preferred_reading.text.gsub(/\{[0-9]\}/,'').strip.camelwords
|
142
|
+
end
|
143
|
+
when @from && !@from_role # @from is an objectified fact type so @from_role is a phantom
|
144
|
+
@from.name.camelwords
|
145
|
+
when !@from_role # Self-value role of an independent ValueType
|
146
|
+
@from.name.camelwords + ["Value"]
|
147
|
+
when @from_role.role_name # Named role
|
148
|
+
@from_role.role_name.camelwords
|
149
|
+
else # Use the name from the preferred reading
|
150
|
+
role_ref = @from_role.preferred_reference
|
151
|
+
[role_ref.leading_adjective, @from_role.object_type.name, role_ref.trailing_adjective].compact.map{|w| w.camelwords}.flatten.reject{|s| s == ''}
|
131
152
|
end
|
132
153
|
end
|
133
154
|
|
@@ -172,7 +193,7 @@ module ActiveFacts
|
|
172
193
|
|
173
194
|
# The reading for the fact type underlying this Reference
|
174
195
|
def reading
|
175
|
-
is_self_value ? "#{from.name} has value" : @fact_type.default_reading
|
196
|
+
is_self_value ? "#{from.name} has value" : @fact_type.default_reading
|
176
197
|
end
|
177
198
|
|
178
199
|
def inspect #:nodoc:
|
@@ -182,11 +203,11 @@ module ActiveFacts
|
|
182
203
|
end
|
183
204
|
|
184
205
|
module Metamodel #:nodoc:
|
185
|
-
class
|
206
|
+
class ObjectType
|
186
207
|
# Say whether the independence of this object is still under consideration
|
187
208
|
# This is used in detecting dependency cycles, such as occurs in the Metamodel
|
188
209
|
attr_accessor :tentative #:nodoc:
|
189
|
-
attr_writer :is_table # The two
|
210
|
+
attr_writer :is_table # The two ObjectType subclasses provide the attr_reader method
|
190
211
|
|
191
212
|
def show_tabular #:nodoc:
|
192
213
|
(tentative ? "tentatively " : "") +
|
@@ -213,17 +234,17 @@ module ActiveFacts
|
|
213
234
|
@tentative = true
|
214
235
|
end
|
215
236
|
|
216
|
-
# References from this
|
237
|
+
# References from this ObjectType
|
217
238
|
def references_from
|
218
239
|
@references_from ||= []
|
219
240
|
end
|
220
241
|
|
221
|
-
# References to this
|
242
|
+
# References to this ObjectType
|
222
243
|
def references_to
|
223
244
|
@references_to ||= []
|
224
245
|
end
|
225
246
|
|
226
|
-
# True if this
|
247
|
+
# True if this ObjectType has any References (to or from)
|
227
248
|
def has_references #:nodoc:
|
228
249
|
@references_from || @references_to
|
229
250
|
end
|
@@ -236,7 +257,11 @@ module ActiveFacts
|
|
236
257
|
|
237
258
|
def populate_references #:nodoc:
|
238
259
|
all_role.each do |role|
|
239
|
-
|
260
|
+
# It's possible that this role is in an implicit or derived fact type. Skip it if so.
|
261
|
+
next if role.fact_type.is_a?(ImplicitFactType) or
|
262
|
+
role.fact_type.preferred_reading.role_sequence.all_role_ref.to_a[0].join_role
|
263
|
+
|
264
|
+
populate_reference role
|
240
265
|
end
|
241
266
|
end
|
242
267
|
|
@@ -321,12 +346,12 @@ module ActiveFacts
|
|
321
346
|
r.tabulate
|
322
347
|
end
|
323
348
|
else
|
324
|
-
raise "
|
349
|
+
raise "Role #{role.object_type.name} in '#{role.fact_type.default_reading}' lacks a uniqueness constraint"
|
325
350
|
end
|
326
351
|
end
|
327
352
|
end
|
328
353
|
|
329
|
-
class EntityType <
|
354
|
+
class EntityType < ObjectType
|
330
355
|
def populate_references #:nodoc:
|
331
356
|
if fact_type && fact_type.all_role.size > 1
|
332
357
|
# NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead:
|
@@ -340,23 +365,23 @@ module ActiveFacts
|
|
340
365
|
|
341
366
|
class Vocabulary
|
342
367
|
def populate_all_references #:nodoc:
|
343
|
-
debug :references, "Populating all
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
368
|
+
debug :references, "Populating all object_type references" do
|
369
|
+
all_object_type.each do |object_type|
|
370
|
+
object_type.clear_references
|
371
|
+
object_type.is_table = nil # Undecided; force an attempt to decide
|
372
|
+
object_type.tentative = true # Uncertain
|
348
373
|
end
|
349
|
-
|
350
|
-
debug :references, "Populating references for #{
|
351
|
-
|
374
|
+
all_object_type.each do |object_type|
|
375
|
+
debug :references, "Populating references for #{object_type.name}" do
|
376
|
+
object_type.populate_references
|
352
377
|
end
|
353
378
|
end
|
354
379
|
end
|
355
|
-
debug :references, "Finished
|
356
|
-
|
357
|
-
next unless
|
358
|
-
debug :references, "#{
|
359
|
-
|
380
|
+
debug :references, "Finished object_type references" do
|
381
|
+
all_object_type.each do |object_type|
|
382
|
+
next unless object_type.references_from.size > 0
|
383
|
+
debug :references, "#{object_type.name}:" do
|
384
|
+
object_type.references_from.each do |ref|
|
360
385
|
debug :references, "#{ref}"
|
361
386
|
end
|
362
387
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Relational mapping and persistence.
|
3
3
|
# Tables; Calculate the relational composition of a given Vocabulary.
|
4
|
-
# The composition consists of decisions about which
|
4
|
+
# The composition consists of decisions about which ObjectTypes are tables,
|
5
5
|
# and what columns (absorbed roled) those tables will have.
|
6
6
|
#
|
7
7
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
@@ -17,7 +17,7 @@ require 'activefacts/persistence/reference'
|
|
17
17
|
module ActiveFacts
|
18
18
|
module Metamodel
|
19
19
|
|
20
|
-
class ValueType <
|
20
|
+
class ValueType < ObjectType
|
21
21
|
def absorbed_via #:nodoc:
|
22
22
|
# ValueTypes aren't absorbed in the way EntityTypes are
|
23
23
|
nil
|
@@ -55,7 +55,7 @@ module ActiveFacts
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
class EntityType <
|
58
|
+
class EntityType < ObjectType
|
59
59
|
# A Reference from an entity type that fully absorbs this one
|
60
60
|
def absorbed_via; @absorbed_via; end
|
61
61
|
def absorbed_via=(r) #:nodoc:
|
@@ -81,13 +81,15 @@ module ActiveFacts
|
|
81
81
|
# Always a table if nowhere else to go, and has no one-to-ones that might flip:
|
82
82
|
if references_to.empty? and
|
83
83
|
!references_from.detect{|ref| ref.role_type == :one_one }
|
84
|
-
debug :absorption, "EntityType #{name} is independent as it has nowhere to go"
|
84
|
+
debug :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
|
85
85
|
return @is_table = true
|
86
86
|
end
|
87
87
|
|
88
88
|
# Subtypes are not a table unless partitioned or separate
|
89
89
|
# REVISIT: Support partitioned subtypes here
|
90
90
|
if (!supertypes.empty?)
|
91
|
+
as_ti = all_supertype_inheritance.detect{|ti| ti.assimilation}
|
92
|
+
debug :absorption, "EntityType #{name} is #{as_ti ? as_ti.assimilation+' from' : 'absorbed into'} supertype #{(as_ti ? as_ti.supertype : supertypes[0]).name}"
|
91
93
|
return @is_table = all_supertype_inheritance.detect{|ti| ti.assimilation} != nil
|
92
94
|
end
|
93
95
|
|
@@ -96,8 +98,8 @@ module ActiveFacts
|
|
96
98
|
# to manage the auto-assignment.
|
97
99
|
if references_to.size > 1 and
|
98
100
|
preferred_identifier.role_sequence.all_role_ref.detect {|rr|
|
99
|
-
next false unless rr.role.
|
100
|
-
rr.role.
|
101
|
+
next false unless rr.role.object_type.is_a? ValueType
|
102
|
+
rr.role.object_type.is_auto_assigned
|
101
103
|
}
|
102
104
|
debug :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
|
103
105
|
@tentative = false
|
@@ -113,7 +115,7 @@ module ActiveFacts
|
|
113
115
|
def role_type
|
114
116
|
# TypeInheritance roles are always 1:1
|
115
117
|
if TypeInheritance === fact_type
|
116
|
-
return
|
118
|
+
return object_type == fact_type.supertype ? :supertype : :subtype
|
117
119
|
end
|
118
120
|
|
119
121
|
# Always N:1 if unary:
|
@@ -156,7 +158,7 @@ module ActiveFacts
|
|
156
158
|
end
|
157
159
|
|
158
160
|
class Vocabulary
|
159
|
-
# return an Array of
|
161
|
+
# return an Array of ObjectTypes that will have their own tables
|
160
162
|
def tables
|
161
163
|
decide_tables if !@tables
|
162
164
|
@tables
|
@@ -164,9 +166,9 @@ module ActiveFacts
|
|
164
166
|
|
165
167
|
def decide_tables #:nodoc:
|
166
168
|
# Strategy:
|
167
|
-
# 1) Populate references for all
|
168
|
-
# 2) Decide which
|
169
|
-
# a.
|
169
|
+
# 1) Populate references for all ObjectTypes
|
170
|
+
# 2) Decide which ObjectTypes must be and must not be tables
|
171
|
+
# a. ObjectTypes labelled is_independent are tables (See the is_table methods above)
|
170
172
|
# b. Entity types having no references to them must be tables
|
171
173
|
# c. subtypes are not tables unless marked with assimilation = separate or partitioned
|
172
174
|
# d. ValueTypes are never tables unless they can have references (to other ValueTypes)
|
@@ -181,18 +183,18 @@ module ActiveFacts
|
|
181
183
|
populate_all_references
|
182
184
|
|
183
185
|
debug :absorption, "Calculating relational composition" do
|
184
|
-
# Evaluate the possible independence of each
|
186
|
+
# Evaluate the possible independence of each object_type, building an array of object_types of indeterminate status:
|
185
187
|
undecided =
|
186
|
-
|
187
|
-
|
188
|
-
|
188
|
+
all_object_type.select do |object_type|
|
189
|
+
object_type.is_table # Ask it whether it thinks it should be a table
|
190
|
+
object_type.tentative # Selection criterion
|
189
191
|
end
|
190
192
|
|
191
|
-
if debug :absorption, "Generating tables, #{undecided.size} undecided"
|
192
|
-
(
|
193
|
-
next if ValueType ===
|
193
|
+
if debug :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
|
194
|
+
(all_object_type-undecided).each {|object_type|
|
195
|
+
next if ValueType === object_type && !object_type.is_table # Skip unremarkable cases
|
194
196
|
debug :absorption do
|
195
|
-
debug :absorption, "#{
|
197
|
+
debug :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
|
196
198
|
end
|
197
199
|
}
|
198
200
|
end
|
@@ -203,50 +205,50 @@ module ActiveFacts
|
|
203
205
|
debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
|
204
206
|
possible_flips = {} # A hash by table containing an array of references that can be flipped
|
205
207
|
finalised = # Make an array of things we finalised during this pass
|
206
|
-
undecided.select do |
|
207
|
-
debug :absorption, "Considering #{
|
208
|
-
debug :absorption, "refs to #{
|
209
|
-
debug :absorption, "refs from #{
|
208
|
+
undecided.select do |object_type|
|
209
|
+
debug :absorption, "Considering #{object_type.name}:" do
|
210
|
+
debug :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
|
211
|
+
debug :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
|
210
212
|
|
211
213
|
# Always absorb an objectified unary into its role player:
|
212
|
-
if
|
213
|
-
debug :absorption, "Absorb objectified unary #{
|
214
|
-
|
215
|
-
next
|
214
|
+
if object_type.fact_type && object_type.fact_type.all_role.size == 1
|
215
|
+
debug :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
|
216
|
+
object_type.definitely_not_table
|
217
|
+
next object_type
|
216
218
|
end
|
217
219
|
|
218
220
|
# If the PI contains one role only, played by an entity type that can absorb us, do that.
|
219
|
-
pi_roles =
|
220
|
-
debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.
|
221
|
+
pi_roles = object_type.preferred_identifier.role_sequence.all_role_ref.map(&:role)
|
222
|
+
debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
|
221
223
|
first_pi_role = pi_roles[0]
|
222
224
|
pi_ref = nil
|
223
225
|
if pi_roles.size == 1 and
|
224
|
-
|
226
|
+
object_type.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
|
225
227
|
|
226
|
-
debug :absorption, "#{
|
227
|
-
|
228
|
-
next
|
228
|
+
debug :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
|
229
|
+
object_type.definitely_not_table
|
230
|
+
next object_type
|
229
231
|
end
|
230
232
|
|
231
233
|
# If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
|
232
234
|
non_identifying_refs_from =
|
233
|
-
|
235
|
+
object_type.references_from.reject{|ref|
|
234
236
|
pi_roles.include?(ref.to_role)
|
235
237
|
}
|
236
|
-
debug :absorption, "#{
|
238
|
+
debug :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
|
237
239
|
|
238
|
-
if
|
240
|
+
if object_type.references_to.size > 1 and
|
239
241
|
non_identifying_refs_from.size > 0
|
240
|
-
debug :absorption, "#{
|
241
|
-
|
242
|
-
next
|
242
|
+
debug :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
|
243
|
+
object_type.definitely_table
|
244
|
+
next object_type
|
243
245
|
end
|
244
246
|
|
245
247
|
absorption_paths =
|
246
248
|
(
|
247
249
|
non_identifying_refs_from.reject do |ref|
|
248
250
|
!ref.to or ref.to.absorbed_via == ref
|
249
|
-
end+
|
251
|
+
end+object_type.references_to
|
250
252
|
).reject do |ref|
|
251
253
|
next true if !ref.to.is_table or
|
252
254
|
![:one_one, :supertype, :subtype].include?(ref.role_type)
|
@@ -255,29 +257,29 @@ module ActiveFacts
|
|
255
257
|
from_is_mandatory = !!ref.is_mandatory
|
256
258
|
to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
|
257
259
|
|
258
|
-
bad = !(ref.from ==
|
259
|
-
debug :absorption, "Not absorbing #{
|
260
|
+
bad = !(ref.from == object_type ? from_is_mandatory : to_is_mandatory)
|
261
|
+
debug :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
|
260
262
|
bad
|
261
263
|
end
|
262
264
|
|
263
265
|
# If this object can be fully absorbed, do that (might require flipping some references)
|
264
266
|
if absorption_paths.size > 0
|
265
|
-
debug :absorption, "#{
|
267
|
+
debug :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
|
266
268
|
absorption_paths.each do |ref|
|
267
|
-
debug :absorption, "flip #{ref} so #{
|
268
|
-
ref.flip if
|
269
|
+
debug :absorption, "flip #{ref} so #{object_type.name} can be absorbed"
|
270
|
+
ref.flip if object_type == ref.from
|
269
271
|
end
|
270
|
-
|
271
|
-
next
|
272
|
+
object_type.definitely_not_table
|
273
|
+
next object_type
|
272
274
|
end
|
273
275
|
|
274
276
|
if non_identifying_refs_from.size == 0
|
275
|
-
# and (!
|
277
|
+
# and (!object_type.is_a?(EntityType) ||
|
276
278
|
# # REVISIT: The roles may be collectively but not individually mandatory.
|
277
|
-
#
|
278
|
-
debug :absorption, "#{
|
279
|
-
|
280
|
-
next
|
279
|
+
# object_type.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
|
280
|
+
debug :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
|
281
|
+
object_type.definitely_not_table
|
282
|
+
next object_type
|
281
283
|
end
|
282
284
|
|
283
285
|
false # Failed to decide about this entity_type this time around
|
@@ -290,18 +292,18 @@ module ActiveFacts
|
|
290
292
|
|
291
293
|
# A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
|
292
294
|
# unless it should absorb something else (another ValueType is all it could be):
|
293
|
-
|
294
|
-
if (!
|
295
|
-
debug :absorption, "Making #{
|
296
|
-
|
295
|
+
all_object_type.each do |object_type|
|
296
|
+
if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0)
|
297
|
+
debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
|
298
|
+
object_type.probably_table
|
297
299
|
end
|
298
300
|
end
|
299
301
|
|
300
302
|
# Now, evaluate all possibilities of the tentative assignments
|
301
303
|
# Incomplete. Apparently unnecessary as well... so far. We'll see.
|
302
304
|
if debug :absorption
|
303
|
-
undecided.each do |
|
304
|
-
debug :absorption, "Unable to decide independence of #{
|
305
|
+
undecided.each do |object_type|
|
306
|
+
debug :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
|
305
307
|
end
|
306
308
|
end
|
307
309
|
end
|
@@ -310,7 +312,7 @@ module ActiveFacts
|
|
310
312
|
populate_all_indices
|
311
313
|
|
312
314
|
@tables =
|
313
|
-
|
315
|
+
all_object_type.
|
314
316
|
select { |f| f.is_table }.
|
315
317
|
sort_by { |table| table.name }
|
316
318
|
end
|