activefacts 0.8.9 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|