activefacts 0.6.0 → 0.7.0
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/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
|