activefacts 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +7 -2
- data/examples/CQL/Address.cql +0 -2
- data/examples/CQL/Blog.cql +2 -2
- data/examples/CQL/CompanyDirectorEmployee.cql +1 -1
- data/examples/CQL/Death.cql +1 -1
- data/examples/CQL/Metamodel.cql +5 -5
- data/examples/CQL/MultiInheritance.cql +2 -0
- data/examples/CQL/PersonPlaysGame.cql +1 -1
- data/lib/activefacts/cql/Concepts.treetop +17 -8
- data/lib/activefacts/cql/Language/English.treetop +1 -2
- data/lib/activefacts/generate/absorption.rb +1 -1
- data/lib/activefacts/generate/null.rb +8 -1
- data/lib/activefacts/generate/oo.rb +174 -0
- data/lib/activefacts/generate/ruby.rb +49 -208
- data/lib/activefacts/generate/sql/server.rb +137 -72
- data/lib/activefacts/generate/text.rb +1 -1
- data/lib/activefacts/input/orm.rb +12 -2
- data/lib/activefacts/persistence.rb +5 -1
- data/lib/activefacts/persistence/columns.rb +324 -0
- data/lib/activefacts/persistence/foreignkey.rb +87 -0
- data/lib/activefacts/persistence/index.rb +171 -0
- data/lib/activefacts/persistence/reference.rb +326 -0
- data/lib/activefacts/persistence/tables.rb +307 -0
- data/lib/activefacts/support.rb +1 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +42 -5
- data/spec/absorption_spec.rb +8 -6
- data/spec/cql_cql_spec.rb +1 -0
- data/spec/cql_sql_spec.rb +2 -1
- data/spec/cql_unit_spec.rb +0 -6
- data/spec/norma_cql_spec.rb +1 -0
- data/spec/norma_sql_spec.rb +1 -1
- data/spec/norma_tables_spec.rb +41 -43
- metadata +9 -4
- data/lib/activefacts/persistence/composition.rb +0 -653
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveFacts
|
2
|
+
module Metamodel
|
3
|
+
|
4
|
+
class ForeignKey
|
5
|
+
attr_reader :from, :to, :reference, :from_columns, :to_columns
|
6
|
+
def initialize(from, to, fk_ref, from_columns, to_columns)
|
7
|
+
@from, @to, @fk_ref, @from_columns, @to_columns =
|
8
|
+
from, to, fk_ref, from_columns, to_columns
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Concept
|
13
|
+
def all_absorbed_foreign_key_reference_path
|
14
|
+
references_from.inject([]) do |array, ref|
|
15
|
+
if ref.is_simple_reference
|
16
|
+
array << [ref]
|
17
|
+
elsif ref.is_absorbing
|
18
|
+
ref.to.all_absorbed_foreign_key_reference_path.each{|aref|
|
19
|
+
array << aref.insert(0, ref)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
array
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def foreign_keys
|
27
|
+
fk_ref_paths = all_absorbed_foreign_key_reference_path
|
28
|
+
|
29
|
+
# Get the ForeignKey object for each absorbed reference path
|
30
|
+
fk_ref_paths.map do |fk_ref_path|
|
31
|
+
debug :fk, "\nFK: " + fk_ref_path.map{|fk_ref| fk_ref.reading }*" and " do
|
32
|
+
|
33
|
+
from_columns = columns.select{|column|
|
34
|
+
column.references[0...fk_ref_path.size] == fk_ref_path
|
35
|
+
}
|
36
|
+
debug :fk, "from_columns = #{from_columns.map { |column| column.name }*", "}"
|
37
|
+
|
38
|
+
absorption_path = []
|
39
|
+
to = fk_ref_path.last.to
|
40
|
+
# REVISIT: There should be a better way to find where it's absorbed (especially since this fails for absorbed subtypes having their own identification!)
|
41
|
+
while (r = to.absorbed_via)
|
42
|
+
absorption_path << r
|
43
|
+
to = r.to == to ? r.from : r.to
|
44
|
+
end
|
45
|
+
raise "REVISIT: #{fk_ref_path.inspect} is bad" unless to and to.columns
|
46
|
+
|
47
|
+
unless absorption_path.empty?
|
48
|
+
debug :fk, "Reference target #{fk_ref_path.last.to.name} is absorbed into #{to.name} via:" do
|
49
|
+
debug :fk, "#{absorption_path.map(&:reading)*" and "}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
debug :fk, "Looking at absorption depth of #{absorption_path.size} in #{to.name} for to_columns for #{from_columns.map(&:name)*", "}:"
|
54
|
+
to_supertypes = to.supertypes_transitive
|
55
|
+
to_columns = from_columns.map do |from_column|
|
56
|
+
debug :fk, "\tLooking for counterpart of #{from_column.name}: #{from_column.comment}" do
|
57
|
+
target_path = absorption_path + from_column.references[fk_ref_path.size..-1]
|
58
|
+
debug :fk, "\tcounterpart MUST MATCH #{target_path.map(&:reading)*" and "}"
|
59
|
+
c = to.columns.detect do |column|
|
60
|
+
debug :fk, "Considering #{column.references.map(&:reading) * " and "}"
|
61
|
+
debug :fk, "exact match: #{column.name}: #{column.comment}" if column.references == target_path
|
62
|
+
# Column may be inherited into "to", in which case target_path is too long.
|
63
|
+
cr = column.references
|
64
|
+
allowed_type = fk_ref_path.last.to
|
65
|
+
#debug :fk, "Check for absorption, need #{allowed_type.name}" if cr != target_path
|
66
|
+
cr == target_path or
|
67
|
+
cr == target_path[-cr.size..-1] &&
|
68
|
+
!target_path[0...-cr.size].detect do |ref|
|
69
|
+
ft = ref.fact_type
|
70
|
+
next true if allowed_type.absorbed_via != ref # Problems if it doesn't match
|
71
|
+
allowed_type = ref.from
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
raise "REVISIT: Failed to find conterpart column for #{from_column.name}" unless c
|
76
|
+
c
|
77
|
+
end
|
78
|
+
end
|
79
|
+
debug :fk, "to_columns in #{to.name}: #{to_columns.map { |column| column ? column.name : "OOPS!" }*", "}"
|
80
|
+
|
81
|
+
ForeignKey.new(self, to, fk_ref_path[-1], from_columns, to_columns)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
#
|
2
|
+
# An Index on a Concept is used to represent a unique constraint across roles absorbed
|
3
|
+
# into that concept's table.
|
4
|
+
#
|
5
|
+
# Reference objects update each concept's list of the references *to* and *from* that concept.
|
6
|
+
#
|
7
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
8
|
+
#
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Metamodel
|
12
|
+
class Index
|
13
|
+
attr_reader :uniqueness_constraint, :on, :over, :columns, :is_primary, :is_unique
|
14
|
+
|
15
|
+
# An Index arises from a uniqueness constraint and applies to a table,
|
16
|
+
# but because the UC may actually be over an object absorbed into the table,
|
17
|
+
# we must record that object also.
|
18
|
+
# We record the columns it's over, whether it's primary (for 'over'),
|
19
|
+
# and whether it's unique (always, at present)
|
20
|
+
def initialize(uc, on, over, columns, is_primary, is_unique = true)
|
21
|
+
@uniqueness_constraint, @on, @over, @columns, @is_primary, @is_unique =
|
22
|
+
uc, on, over, columns, is_primary, is_unique
|
23
|
+
end
|
24
|
+
|
25
|
+
def real_name
|
26
|
+
@uniqueness_constraint.name && @uniqueness_constraint.name != '' ? @uniqueness_constraint.name : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
uc = @uniqueness_constraint
|
31
|
+
r = real_name
|
32
|
+
return r if r && r !~ /^(Ex|In)ternalUniquenessConstraint[0-9]+$/
|
33
|
+
(uc.is_preferred_identifier ? "PK_" : "IX_") +
|
34
|
+
view_name +
|
35
|
+
(uc.is_preferred_identifier ? "" : "By"+column_names*"")
|
36
|
+
end
|
37
|
+
|
38
|
+
def abbreviated_column_names
|
39
|
+
columns.map{|column| column.name.sub(/^#{over.name}/,'')}
|
40
|
+
end
|
41
|
+
|
42
|
+
def column_names
|
43
|
+
columns.map{|column| column.name}
|
44
|
+
end
|
45
|
+
|
46
|
+
def view_name
|
47
|
+
"#{over.name}#{on == over ? "" : "In"+on.name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
name = @uniqueness_constraint.name
|
52
|
+
colnames = @columns.map(&:name)*", "
|
53
|
+
preferred = @uniqueness_constraint.is_preferred_identifier ? " (preferred)" : ""
|
54
|
+
"Index #{name} on #{@on.name} over #{@over.name}(#{colnames})#{preferred}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Concept
|
59
|
+
attr_reader :indices
|
60
|
+
|
61
|
+
def clear_indices
|
62
|
+
# Clear any previous indices
|
63
|
+
@indices = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def populate_indices
|
67
|
+
# The absorption path of a column indicates how it came to be in this table.
|
68
|
+
# It might be a direct many:one valuetype relationship, or it might be in such
|
69
|
+
# a relationship to an entity that was absorbed into this table (and so on).
|
70
|
+
# The reference path is the set of absorption references and one past it.
|
71
|
+
# Stopping here means we don't dig into the definitions of FK column counterparts.
|
72
|
+
# Note that many columns of an object may have the same ref_path.
|
73
|
+
all_column_by_ref_path =
|
74
|
+
debug :index2, "Indexing columns by ref_path" do
|
75
|
+
columns.inject({}) do |hash, column|
|
76
|
+
debug :index2, "References in column #{name}#{column.name}" do
|
77
|
+
ref_path = column.absorption_references
|
78
|
+
raise "No absorption_references for #{column.name} from #{column.references.map(&:to_s)*" and "}" if !ref_path || ref_path.empty?
|
79
|
+
(hash[ref_path] ||= []) << column
|
80
|
+
debug :index2, "#{column.name} involves #{ref_path.map(&:to_s)*" and "}"
|
81
|
+
end
|
82
|
+
hash
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
columns_by_unique_constraint = {}
|
87
|
+
all_column_by_role_ref =
|
88
|
+
all_column_by_ref_path.
|
89
|
+
keys. # Go through all refpaths and find uniqueness constraints
|
90
|
+
inject({}) do |hash, ref_path|
|
91
|
+
ref_path.each do |ref|
|
92
|
+
next unless ref.to_role
|
93
|
+
ref.to_role.all_role_ref.each do |role_ref|
|
94
|
+
pcs = role_ref.role_sequence.all_presence_constraint.
|
95
|
+
reject do |pc|
|
96
|
+
!pc.max_frequency or # No maximum freq; cannot be a uniqueness constraint
|
97
|
+
pc.max_frequency != 1 or # maximum is not 1
|
98
|
+
pc.role_sequence.all_role_ref.size == 1 && # UniquenessConstraint is over one role
|
99
|
+
(pc.role_sequence.all_role_ref[0].role.fact_type.is_a?(TypeInheritance) || # Inheritance
|
100
|
+
pc.role_sequence.all_role_ref[0].role.fact_type.all_role.size == 1) # Unary
|
101
|
+
# The preceeeding two restrictions exclude the internal UCs created within NORMA.
|
102
|
+
end
|
103
|
+
next unless pcs.size > 0
|
104
|
+
# The columns for this ref_path support the UCs in "pcs".
|
105
|
+
pcs.each do |pc|
|
106
|
+
(columns_by_unique_constraint[pc] ||= []).concat(all_column_by_ref_path[ref_path])
|
107
|
+
end
|
108
|
+
hash[role_ref] = all_column_by_ref_path[ref_path]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
hash
|
112
|
+
end
|
113
|
+
|
114
|
+
debug :index, "All Indices in #{name}:" do
|
115
|
+
@indices = columns_by_unique_constraint.map do |uc, columns|
|
116
|
+
absorption_level = columns.map(&:absorption_level).min
|
117
|
+
over = columns[0].references[absorption_level].from
|
118
|
+
|
119
|
+
# Absorption through a one-to-one forms a UC that we don't need to enforce using an index:
|
120
|
+
next if over != self and
|
121
|
+
over.absorbed_via == columns[0].references[absorption_level-1] and
|
122
|
+
(rrs = uc.role_sequence.all_role_ref).size == 1 and
|
123
|
+
over.absorbed_via.fact_type.all_role.include?(rrs[0].role)
|
124
|
+
|
125
|
+
index = Index.new(
|
126
|
+
uc,
|
127
|
+
self,
|
128
|
+
over,
|
129
|
+
columns,
|
130
|
+
uc.is_preferred_identifier
|
131
|
+
)
|
132
|
+
debug :index, index
|
133
|
+
index
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
class Vocabulary
|
141
|
+
def populate_all_indices
|
142
|
+
debug :index, "Populating all concept indices" do
|
143
|
+
all_feature.each do |feature|
|
144
|
+
next unless feature.is_a? Concept
|
145
|
+
feature.clear_indices
|
146
|
+
end
|
147
|
+
all_feature.each do |feature|
|
148
|
+
next unless feature.is_a? Concept
|
149
|
+
next unless feature.is_table
|
150
|
+
debug :index, "Populating indices for #{feature.name}" do
|
151
|
+
feature.populate_indices
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
debug :index, "Finished concept indices" do
|
156
|
+
all_feature.each do |feature|
|
157
|
+
next unless feature.is_a? Concept
|
158
|
+
next unless feature.is_table
|
159
|
+
next unless feature.indices.size > 0
|
160
|
+
debug :index, "#{feature.name}:" do
|
161
|
+
feature.indices.each do |index|
|
162
|
+
debug :index, index
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
#
|
2
|
+
# A Reference from one Concept to another is created for each many-1 or 1-1 relationship
|
3
|
+
# (including subtyping), and also for a unary role (implicitly to Boolean concept).
|
4
|
+
# A 1-1 or subtyping reference should be created in only one direction, and may be flipped
|
5
|
+
# if needed.
|
6
|
+
#
|
7
|
+
# A reference to a concept that's a table or is fully absorbed into a table will
|
8
|
+
# become a foreign key, otherwise it will absorb all that concept's references.
|
9
|
+
#
|
10
|
+
# Reference objects update each concept's list of the references *to* and *from* that concept.
|
11
|
+
#
|
12
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
13
|
+
#
|
14
|
+
# REVISIT: References need is_mandatory
|
15
|
+
# REVISIT: Need to index References by to_role, to help in finding PK references etc.
|
16
|
+
|
17
|
+
module ActiveFacts
|
18
|
+
module Metamodel
|
19
|
+
|
20
|
+
class Reference
|
21
|
+
attr_reader :from, :to # A "from" instance is related to one "to" instance
|
22
|
+
attr_reader :from_role, :to_role # For objectified facts, one role will be nil (a phantom)
|
23
|
+
attr_reader :fact_type
|
24
|
+
|
25
|
+
# A Reference is created from a concept in regard to a role it plays
|
26
|
+
def initialize(from, role)
|
27
|
+
@from = from
|
28
|
+
return unless role # All done if it's a self-value reference for a ValueType
|
29
|
+
@fact_type = role.fact_type
|
30
|
+
if @fact_type.all_role.size == 1
|
31
|
+
# @from_role is nil for a unary
|
32
|
+
@to_role = role
|
33
|
+
@to = role.fact_type.entity_type # nil unless the unary is objectified
|
34
|
+
elsif (role.fact_type.entity_type == @from) # role is in "from", an objectified fact type
|
35
|
+
@from_role = nil # Phantom role
|
36
|
+
@to_role = role
|
37
|
+
@to = @to_role.concept
|
38
|
+
else
|
39
|
+
@from_role = role
|
40
|
+
@to = role.fact_type.entity_type # If set, to_role is a phantom
|
41
|
+
unless @to
|
42
|
+
raise "Illegal reference through >binary fact type" if @fact_type.all_role.size >2
|
43
|
+
@to_role = (role.fact_type.all_role-[role])[0]
|
44
|
+
@to = @to_role.concept
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def role_type
|
50
|
+
role = @from_role||@to_role
|
51
|
+
role && role.role_type
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_mandatory
|
55
|
+
!@from_role || # All phantom roles of fact types are mandatory
|
56
|
+
is_unary || # Unary fact types become booleans, which must be true or false
|
57
|
+
@from_role.is_mandatory
|
58
|
+
end
|
59
|
+
|
60
|
+
def is_unary
|
61
|
+
!@to && @to_role && @to_role.fact_type.all_role.size == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# This case is the only one that cannot be used in the preferred identifier of @from
|
65
|
+
def is_to_objectified_fact
|
66
|
+
@to && !@to_role && @from_role
|
67
|
+
end
|
68
|
+
|
69
|
+
def is_from_objectified_fact
|
70
|
+
@to && @to_role && !@from_role
|
71
|
+
end
|
72
|
+
|
73
|
+
def is_self_value
|
74
|
+
!@to && !@to_role
|
75
|
+
end
|
76
|
+
|
77
|
+
def is_absorbing
|
78
|
+
@to && @to.absorbed_via == self
|
79
|
+
end
|
80
|
+
|
81
|
+
def is_simple_reference
|
82
|
+
# It's a simple reference to a thing if that thing is a table,
|
83
|
+
# or is fully absorbed into another table but not via this reference.
|
84
|
+
@to && (@to.is_table or @to.absorbed_via && !is_absorbing)
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_names
|
88
|
+
case
|
89
|
+
when is_unary
|
90
|
+
@to_role.fact_type.preferred_reading.reading_text.gsub(/\{[0-9]\}/,'').strip.split(/\s/)
|
91
|
+
when @to && !@to_role # @to is an objectified fact type so @to_role is a phantom
|
92
|
+
[@to.name]
|
93
|
+
when !@to_role # Self-value role of an independent ValueType
|
94
|
+
["#{@from.name}Value"]
|
95
|
+
when @to_role.role_name # Named role
|
96
|
+
[@to_role.role_name]
|
97
|
+
else # Use the name from the preferred reading
|
98
|
+
role_ref = @to_role.preferred_reference
|
99
|
+
[role_ref.leading_adjective, @to_role.concept.name, role_ref.trailing_adjective].compact.map{|w| w.split(/\s/)}.flatten.reject{|s| s == ''}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# For a one-to-one (or a subtyping fact type), reverse the direction:
|
104
|
+
def flip
|
105
|
+
raise "Illegal flip of #{self}" unless @to and [:one_one, :subtype, :supertype].include?(role_type)
|
106
|
+
|
107
|
+
detabulate
|
108
|
+
|
109
|
+
if @to.absorbed_via == self
|
110
|
+
@to.absorbed_via = nil
|
111
|
+
@from.absorbed_via = self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Flip the reference
|
115
|
+
@to, @from = @from, @to
|
116
|
+
@to_role, @from_role = @from_role, @to_role
|
117
|
+
|
118
|
+
tabulate
|
119
|
+
end
|
120
|
+
|
121
|
+
def tabulate
|
122
|
+
# Add to @to and @from's reference lists
|
123
|
+
@from.references_from << self
|
124
|
+
@to.references_to << self if @to # Guard against self-values
|
125
|
+
|
126
|
+
debug :references, "Adding #{to_s}"
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def detabulate
|
131
|
+
# Remove from @to and @from's reference lists if present
|
132
|
+
return unless @from.references_from.delete(self)
|
133
|
+
@to.references_to.delete self if @to # Guard against self-values
|
134
|
+
debug :references, "Dropping #{to_s}"
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
def to_s
|
139
|
+
"reference from #{@from.name}#{@to ? " to #{@to.name}" : ""}" + (@fact_type ? " in '#{@fact_type.default_reading}'" : "")
|
140
|
+
end
|
141
|
+
|
142
|
+
def reading
|
143
|
+
is_self_value ? "#{from.name} has value" : @fact_type.default_reading
|
144
|
+
end
|
145
|
+
|
146
|
+
def inspect; to_s; end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Concept
|
150
|
+
# Say whether the independence of this object is still under consideration
|
151
|
+
# This is used in detecting dependency cycles, such as occurs in the Metamodel
|
152
|
+
attr_accessor :tentative
|
153
|
+
attr_writer :is_table # The two Concept subclasses provide the reader
|
154
|
+
|
155
|
+
def show_tabular
|
156
|
+
(tentative ? "tentatively " : "") +
|
157
|
+
(is_table ? "" : "not ")+"a table"
|
158
|
+
end
|
159
|
+
|
160
|
+
def definitely_table
|
161
|
+
@is_table = true
|
162
|
+
@tentative = false
|
163
|
+
end
|
164
|
+
|
165
|
+
def definitely_not_table
|
166
|
+
@is_table = false
|
167
|
+
@tentative = false
|
168
|
+
end
|
169
|
+
|
170
|
+
def probably_table
|
171
|
+
@is_table = true
|
172
|
+
@tentative = true
|
173
|
+
end
|
174
|
+
|
175
|
+
def probably_not_table
|
176
|
+
@is_table = false
|
177
|
+
@tentative = true
|
178
|
+
end
|
179
|
+
|
180
|
+
def references_from
|
181
|
+
@references_from ||= []
|
182
|
+
end
|
183
|
+
|
184
|
+
def references_to
|
185
|
+
@references_to ||= []
|
186
|
+
end
|
187
|
+
|
188
|
+
def has_references
|
189
|
+
@references_from || @references_to
|
190
|
+
end
|
191
|
+
|
192
|
+
def clear_references
|
193
|
+
# Clear any previous references:
|
194
|
+
@references_to = nil
|
195
|
+
@references_from = nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def populate_references
|
199
|
+
all_role.each do |role|
|
200
|
+
populate_reference role
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def populate_reference role
|
205
|
+
role_type = role.role_type
|
206
|
+
debug :references, "#{name} has #{role_type} role in '#{role.fact_type.describe}'"
|
207
|
+
case role_type
|
208
|
+
when :many_one
|
209
|
+
Reference.new(self, role).tabulate # A simple reference
|
210
|
+
|
211
|
+
when :one_many
|
212
|
+
if role.fact_type.entity_type == self # A Role of this objectified FactType
|
213
|
+
Reference.new(self, role).tabulate # A simple reference; check that
|
214
|
+
else
|
215
|
+
# Can't absorb many of these into one of those
|
216
|
+
#debug :references, "Ignoring #{role_type} reference from #{name} to #{Reference.new(self, role).to.name}"
|
217
|
+
end
|
218
|
+
|
219
|
+
when :unary
|
220
|
+
Reference.new(self, role).tabulate # A simple reference
|
221
|
+
|
222
|
+
when :supertype # A subtype absorbs a reference to its supertype when separate, or all when partitioned
|
223
|
+
# REVISIT: Or when partitioned
|
224
|
+
if role.fact_type.subtype.is_independent
|
225
|
+
debug :references, "supertype #{name} doesn't absorb a reference to separate subtype #{role.fact_type.subtype.name}"
|
226
|
+
else
|
227
|
+
r = Reference.new(self, role)
|
228
|
+
r.to.absorbed_via = r
|
229
|
+
debug :references, "supertype #{name} absorbs subtype #{r.to.name}"
|
230
|
+
r.tabulate
|
231
|
+
end
|
232
|
+
|
233
|
+
when :subtype # This object is a supertype, which can absorb the subtype unless that's independent
|
234
|
+
if is_independent # REVISIT: Or when partitioned
|
235
|
+
Reference.new(self, role).tabulate
|
236
|
+
# If partitioned, the supertype is absorbed into *each* subtype; a reference to the supertype needs to know which
|
237
|
+
else
|
238
|
+
# debug :references, "subtype #{name} is absorbed into #{role.fact_type.supertype.name}"
|
239
|
+
end
|
240
|
+
|
241
|
+
when :one_one
|
242
|
+
r = Reference.new(self, role)
|
243
|
+
|
244
|
+
# Decide which way the one-to-one is likely to go; it will be flipped later if necessary.
|
245
|
+
# Force the decision if just one is independent:
|
246
|
+
r.tabulate and return if is_independent and !r.to.is_independent
|
247
|
+
return if !is_independent and r.to.is_independent
|
248
|
+
|
249
|
+
if is_a?(ValueType)
|
250
|
+
# Never absorb an entity type into a value type
|
251
|
+
return if r.to.is_a?(EntityType) # Don't tabulate it
|
252
|
+
else
|
253
|
+
if r.to.is_a?(ValueType)
|
254
|
+
r.tabulate # Always absorb a value type into an entity type
|
255
|
+
return
|
256
|
+
end
|
257
|
+
|
258
|
+
# Force the decision if one EntityType identifies another:
|
259
|
+
if preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == r.to_role}
|
260
|
+
debug :references, "EntityType #{name} is identified by EntityType #{r.to.name}, so gets absorbed elsewhere"
|
261
|
+
return
|
262
|
+
end
|
263
|
+
if r.to.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role == role}
|
264
|
+
debug :references, "EntityType #{name} identifies EntityType #{r.to.name}, so absorbs it"
|
265
|
+
r.to.absorbed_via = r
|
266
|
+
r.tabulate
|
267
|
+
return
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Either both EntityTypes, or both ValueTypes.
|
272
|
+
# Make an arbitrary (but stable) decision which way to go. We might flip it later.
|
273
|
+
unless r.from.name < r.to.name or
|
274
|
+
(r.from == r.to && references_to.detect{|ref| ref.to_role == role}) # one-to-one self reference, done already
|
275
|
+
r.tabulate
|
276
|
+
end
|
277
|
+
else
|
278
|
+
raise "Illegal role type, #{role.fact_type.describe(role)} no uniqueness constraint"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
class EntityType
|
284
|
+
def populate_references
|
285
|
+
if fact_type && fact_type.all_role.size > 1
|
286
|
+
# NOT: fact_type.all_role.each do |role| # Place roles in the preferred order instead:
|
287
|
+
fact_type.preferred_reading.role_sequence.all_role_ref.map(&:role).each do |role|
|
288
|
+
populate_reference role # Objectified fact role, handled specially
|
289
|
+
end
|
290
|
+
end
|
291
|
+
super
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class Vocabulary
|
296
|
+
def populate_all_references
|
297
|
+
debug :references, "Populating all concept references" do
|
298
|
+
all_feature.each do |feature|
|
299
|
+
next unless feature.is_a? Concept
|
300
|
+
feature.clear_references
|
301
|
+
feature.is_table = nil # Undecided; force an attempt to decide
|
302
|
+
feature.tentative = true # Uncertain
|
303
|
+
end
|
304
|
+
all_feature.each do |feature|
|
305
|
+
next unless feature.is_a? Concept
|
306
|
+
debug :references, "Populating references for #{feature.name}" do
|
307
|
+
feature.populate_references
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
debug :references, "Finished concept references" do
|
312
|
+
all_feature.each do |feature|
|
313
|
+
next unless feature.is_a? Concept
|
314
|
+
next unless feature.references_from.size > 0
|
315
|
+
debug :references, "#{feature.name}:" do
|
316
|
+
feature.references_from.each do |ref|
|
317
|
+
debug :references, "#{ref}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
end
|