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
@@ -10,6 +10,7 @@ module ActiveFacts
|
|
10
10
|
class SQL
|
11
11
|
class SERVER
|
12
12
|
include Metamodel
|
13
|
+
ColumnNameMax = 40
|
13
14
|
|
14
15
|
RESERVED_WORDS = %w{
|
15
16
|
ADD ALL ALTER AND ANY AS ASC AUTHORIZATION BACKUP BEGIN BETWEEN
|
@@ -38,6 +39,7 @@ module ActiveFacts
|
|
38
39
|
@vocabulary = vocabulary
|
39
40
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
40
41
|
@delay_fks = options.include? "delay_fks"
|
42
|
+
@norma = options.include? "norma"
|
41
43
|
end
|
42
44
|
|
43
45
|
def puts s
|
@@ -51,6 +53,7 @@ module ActiveFacts
|
|
51
53
|
|
52
54
|
def escape s
|
53
55
|
# Escape SQL keywords and non-identifiers
|
56
|
+
s = s[0...120]
|
54
57
|
if s =~ /[^A-Za-z0-9_]/ || RESERVED_WORDS[s.upcase]
|
55
58
|
"[#{s}]"
|
56
59
|
else
|
@@ -58,50 +61,41 @@ module ActiveFacts
|
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
61
|
-
# Return
|
62
|
-
def
|
63
|
-
|
64
|
-
"
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
basic_type = case (vt.supertype||vt).name
|
75
|
-
when "AutoCounter"; "int"
|
76
|
-
when "Date"; "datetime"
|
77
|
-
when "UnsignedInteger",
|
78
|
-
"SignedInteger"
|
79
|
-
l = length
|
80
|
-
length = nil
|
81
|
-
case
|
82
|
-
when l <= 8; "tinyint"
|
83
|
-
when l <= 16; "shortint"
|
84
|
-
when l <= 32; "int"
|
64
|
+
# Return SQL type and (modified?) length for the passed NORMA base type
|
65
|
+
def norma_type(type, length)
|
66
|
+
sql_type = case type
|
67
|
+
when "AutoCounter"; "int"
|
68
|
+
when "UnsignedInteger",
|
69
|
+
"SignedInteger",
|
70
|
+
"UnsignedSmallInteger",
|
71
|
+
"SignedSmallInteger",
|
72
|
+
"UnsignedTinyInteger"
|
73
|
+
s = case
|
74
|
+
when length <= 8; "tinyint"
|
75
|
+
when length <= 16; "shortint"
|
76
|
+
when length <= 32; "int"
|
85
77
|
else "bigint"
|
86
78
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
79
|
+
length = nil
|
80
|
+
s
|
81
|
+
when "Decimal"; "decimal"
|
82
|
+
|
83
|
+
when "FixedLengthText"; "char"
|
84
|
+
when "VariableLengthText"; "varchar"
|
85
|
+
when "LargeLengthText"; "text"
|
86
|
+
|
87
|
+
when "DateAndTime"; "datetime"
|
88
|
+
when "Date"; "datetime" # SQLSVR 2K5: "date"
|
89
|
+
when "Time"; "datetime" # SQLSVR 2K5: "time"
|
90
|
+
when "AutoTimestamp"; "timestamp"
|
91
|
+
|
92
|
+
when "Money"; "decimal"
|
93
|
+
when "PictureRawData"; "image"
|
94
|
+
when "VariableLengthRawData"; "varbinary"
|
95
|
+
when "BIT"; "bit"
|
96
|
+
else raise "SQL type unknown for NORMA type #{type}"
|
95
97
|
end
|
96
|
-
|
97
|
-
(
|
98
|
-
# Is there any role along the path that lacks a mandatory constraint?
|
99
|
-
role_ref.output_roles.detect { |role| !role.is_mandatory } ? " NULL" : " NOT NULL"
|
100
|
-
)
|
101
|
-
end
|
102
|
-
|
103
|
-
def column_name(role_ref)
|
104
|
-
escape(role_ref.column_name(nil).map{|n| n.sub(/^[a-z]/){|s| s.upcase}}*"")
|
98
|
+
[sql_type, length]
|
105
99
|
end
|
106
100
|
|
107
101
|
def generate(out = $>)
|
@@ -112,52 +106,123 @@ module ActiveFacts
|
|
112
106
|
delayed_foreign_keys = []
|
113
107
|
|
114
108
|
@vocabulary.tables.sort_by{|table| table.name}.each do |table|
|
115
|
-
tables_emitted[table] = true
|
116
109
|
puts "CREATE TABLE #{escape table.name} ("
|
117
110
|
|
118
|
-
pk = table.
|
119
|
-
|
111
|
+
pk = table.identifier_columns
|
112
|
+
identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
|
120
113
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
"\t#{column_name(role_ref)}\t#{sql_type(role_ref)}"
|
126
|
-
end
|
114
|
+
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
115
|
+
fk_columns = table.columns.select do |column|
|
116
|
+
column.references[0].is_simple_reference
|
117
|
+
end
|
127
118
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
119
|
+
columns = table.columns.map do |column|
|
120
|
+
name = escape column.name("")
|
121
|
+
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
122
|
+
type, params, restrictions = column.type
|
123
|
+
restrictions = [] if (fk_columns.include?(column)) # Don't enforce VT restrictions on FK columns
|
124
|
+
length = params[:length]
|
125
|
+
length &&= length.to_i
|
126
|
+
scale = params[:scale]
|
127
|
+
scale &&= scale.to_i
|
128
|
+
type, length = norma_type(type, length) if @norma
|
129
|
+
sql_type = "#{type}#{
|
130
|
+
if !length
|
131
|
+
""
|
132
|
+
else
|
133
|
+
"(" + length.to_s + (scale ? ", #{scale}" : "") + ")"
|
134
|
+
end
|
135
|
+
}"
|
136
|
+
identity = column == identity_column ? " IDENTITY" : ""
|
137
|
+
null = (column.is_mandatory ? "NOT " : "") + "NULL"
|
138
|
+
check = check_clause(name, restrictions)
|
139
|
+
comment = column.comment
|
140
|
+
[ "-- #{comment}", "#{name}#{padding}#{sql_type}#{identity} #{null}#{check}" ]
|
141
|
+
end.flatten
|
142
|
+
|
143
|
+
pk_def = (pk.detect{|column| !column.is_mandatory} ? "UNIQUE(" : "PRIMARY KEY(") +
|
144
|
+
pk.map{|column| escape column.name("")}*", " +
|
145
|
+
")"
|
138
146
|
|
139
147
|
inline_fks = []
|
140
|
-
table.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
table.foreign_keys.each do |fk|
|
149
|
+
fk_text = "FOREIGN KEY (" +
|
150
|
+
fk.from_columns.map{|column| column.name}*", " +
|
151
|
+
") REFERENCES #{escape fk.to.name} (" +
|
152
|
+
fk.to_columns.map{|column| column.name}*", " +
|
153
|
+
")"
|
154
|
+
if !@delay_fks and # We don't want to delay all Fks
|
155
|
+
(tables_emitted[fk.to] or # The target table has been emitted
|
156
|
+
fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes
|
157
|
+
inline_fks << fk_text
|
158
|
+
else
|
159
|
+
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name}\n\tADD " + fk_text)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
indices = table.indices
|
164
|
+
inline_indices = []
|
165
|
+
delayed_indices = []
|
166
|
+
indices.each do |index|
|
167
|
+
next if index.over == table && index.is_primary # Already did the primary keys
|
168
|
+
abbreviated_column_names = index.abbreviated_column_names*""
|
169
|
+
column_names = index.column_names
|
170
|
+
column_name_list = column_names.map{|n| escape(n)}*", "
|
171
|
+
if index.columns.all?{|column| column.is_mandatory}
|
172
|
+
inline_indices << "UNIQUE(#{column_name_list})"
|
173
|
+
else
|
174
|
+
view_name = escape "#{index.view_name}_#{abbreviated_column_names}"
|
175
|
+
delayed_indices <<
|
176
|
+
%Q{CREATE VIEW dbo.#{view_name} (#{column_name_list}) WITH SCHEMABINDING AS
|
177
|
+
\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name}
|
178
|
+
\tWHERE\t#{
|
179
|
+
index.columns.
|
180
|
+
select{|column| !column.is_mandatory }.
|
181
|
+
map{|column|
|
182
|
+
escape(column.name) + " IS NOT NULL"
|
183
|
+
}*"\n\t AND\t"
|
184
|
+
}
|
185
|
+
GO
|
186
|
+
|
187
|
+
CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.columns.map{|column| column.name}*", "})
|
188
|
+
}
|
189
|
+
end
|
151
190
|
end
|
152
191
|
|
153
|
-
|
192
|
+
tables_emitted[table] = true
|
193
|
+
|
194
|
+
puts("\t" + (columns + [pk_def] + inline_indices + inline_fks)*",\n\t")
|
154
195
|
go ")"
|
196
|
+
delayed_indices.each {|index_text|
|
197
|
+
go index_text
|
198
|
+
}
|
155
199
|
end
|
156
200
|
|
157
201
|
delayed_foreign_keys.each do |fk|
|
158
202
|
go fk
|
159
203
|
end
|
160
204
|
end
|
205
|
+
|
206
|
+
def check_clause(column_name, restrictions)
|
207
|
+
return "" if restrictions.empty?
|
208
|
+
# REVISIT: Merge all restrictions (later; now just use the first)
|
209
|
+
" CHECK(" +
|
210
|
+
restrictions[0].all_allowed_range.map do |ar|
|
211
|
+
vr = ar.value_range
|
212
|
+
min = vr.minimum_bound
|
213
|
+
max = vr.maximum_bound
|
214
|
+
if (min && max && max.value == min.value)
|
215
|
+
"#{column_name} = #{min.value}"
|
216
|
+
else
|
217
|
+
inequalities = [
|
218
|
+
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{min.value}",
|
219
|
+
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{max.value}"
|
220
|
+
].compact
|
221
|
+
inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
|
222
|
+
end
|
223
|
+
end*" OR " +
|
224
|
+
")"
|
225
|
+
end
|
161
226
|
end
|
162
227
|
end
|
163
228
|
end
|
@@ -9,7 +9,7 @@ module ActiveFacts
|
|
9
9
|
class TEXT
|
10
10
|
def initialize(vocabulary)
|
11
11
|
@vocabulary = vocabulary
|
12
|
-
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::Constellation === @vocabulary
|
12
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
13
13
|
end
|
14
14
|
|
15
15
|
def generate(out = $>)
|
@@ -161,8 +161,18 @@ module ActiveFacts
|
|
161
161
|
min = x_range.attributes['MinValue']
|
162
162
|
max = x_range.attributes['MaxValue']
|
163
163
|
q = "'"
|
164
|
-
min =
|
165
|
-
|
164
|
+
min = case min
|
165
|
+
when ""; nil
|
166
|
+
when /[^0-9\.]/; q+min+q
|
167
|
+
when /\./; Float(min)
|
168
|
+
else Integer(min)
|
169
|
+
end
|
170
|
+
max = case max
|
171
|
+
when ""; nil
|
172
|
+
when /[^0-9\.]/; q+max+q
|
173
|
+
when /\./; Float(max)
|
174
|
+
else Integer(max)
|
175
|
+
end
|
166
176
|
# ValueRange takes a minimum and/or a maximum Bound, each takes value and whether inclusive
|
167
177
|
@constellation.ValueRange(
|
168
178
|
min ? [min.to_s, true] : nil,
|
@@ -0,0 +1,324 @@
|
|
1
|
+
#
|
2
|
+
# Each Reference from a Concept creates one or more Columns.
|
3
|
+
# A reference to a simple valuetype creates a single column, as
|
4
|
+
# does a reference to a table entity identified by a single value.
|
5
|
+
#
|
6
|
+
# When referring to a concept that doesn't have its own table,
|
7
|
+
# all references from that concept are absorbed into this one.
|
8
|
+
#
|
9
|
+
# When multiple values identify an entity that does have its own
|
10
|
+
# table, a reference to that entity creates multiple columns,
|
11
|
+
# a multi-part foreign key.
|
12
|
+
#
|
13
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
14
|
+
#
|
15
|
+
|
16
|
+
module ActiveFacts
|
17
|
+
module Metamodel
|
18
|
+
|
19
|
+
class Column
|
20
|
+
attr_reader :references
|
21
|
+
|
22
|
+
def references
|
23
|
+
@references ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
# All references up to and including the first non-absorbing reference
|
27
|
+
def absorption_references
|
28
|
+
@references.inject([]) do |array, ref|
|
29
|
+
array << ref
|
30
|
+
# puts "Column #{name} spans #{ref}, #{ref.is_absorbing ? "" : "not "} absorbing (#{ref.to.name} absorbs via #{ref.to.absorbed_via.inspect})"
|
31
|
+
break array unless ref.is_absorbing
|
32
|
+
array
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def absorption_level
|
37
|
+
l = 0
|
38
|
+
@references.detect do |ref|
|
39
|
+
l += 1 if ref.is_absorbing
|
40
|
+
false
|
41
|
+
end
|
42
|
+
l
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(reference = nil)
|
46
|
+
references << reference if reference
|
47
|
+
end
|
48
|
+
|
49
|
+
def prepend reference
|
50
|
+
references.insert 0, reference
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def name(joiner = "")
|
55
|
+
last_name = ""
|
56
|
+
names = @references.
|
57
|
+
reject do |ref|
|
58
|
+
# Skip any object after the first which is identified by this reference
|
59
|
+
ref != @references[0] and
|
60
|
+
!ref.fact_type.is_a?(TypeInheritance) and
|
61
|
+
ref.to and
|
62
|
+
ref.to.is_a?(EntityType) and
|
63
|
+
(role_refs = ref.to.preferred_identifier.role_sequence.all_role_ref).size == 1 and
|
64
|
+
role_refs[0].role == ref.from_role
|
65
|
+
end.
|
66
|
+
inject([]) do |a, ref|
|
67
|
+
names = ref.to_names
|
68
|
+
|
69
|
+
# When traversing type inheritances, keep the subtype name, not the supertype names as well:
|
70
|
+
if a.size > 0 && ref.fact_type.is_a?(TypeInheritance)
|
71
|
+
a[-1] = names[0] if ref.to == ref.fact_type.subtype # Else we already had the subtype
|
72
|
+
next a
|
73
|
+
end
|
74
|
+
|
75
|
+
# When Xyz is followed by XyzID, truncate that to just ID:
|
76
|
+
names[0] = names[0][last_name.size..-1] if last_name == names[0][0...last_name.size]
|
77
|
+
last_name = names.last
|
78
|
+
|
79
|
+
a += names
|
80
|
+
a
|
81
|
+
end
|
82
|
+
|
83
|
+
# Where the last name is like a reference mode but the preceeding name isn't the identified concept,
|
84
|
+
# strip it down (so turn Driver.PartyID into Driver.ID for example):
|
85
|
+
if names.size > 1 and
|
86
|
+
(et = @references.last.from).is_a?(EntityType) and
|
87
|
+
(role_refs = et.preferred_identifier.role_sequence.all_role_ref).size == 1 and
|
88
|
+
role_refs[0].role == @references.last.to_role and
|
89
|
+
names.last[0...et.name.size].downcase == et.name.downcase
|
90
|
+
names[-1] = names.last[et.name.size..-1]
|
91
|
+
names.pop if names.last == ''
|
92
|
+
end
|
93
|
+
|
94
|
+
name_array = names.map{|n| n.sub(/^[a-z]/){|s| s.upcase}}
|
95
|
+
joiner ? name_array * joiner : name_array
|
96
|
+
end
|
97
|
+
|
98
|
+
def is_mandatory
|
99
|
+
!@references.detect{|ref| !ref.is_mandatory}
|
100
|
+
end
|
101
|
+
|
102
|
+
def is_auto_assigned
|
103
|
+
(to = references[-1].to) && to.is_auto_assigned
|
104
|
+
end
|
105
|
+
|
106
|
+
def type
|
107
|
+
params = {}
|
108
|
+
restrictions = []
|
109
|
+
return ["BIT", params, restrictions] if references[-1].is_unary # It's a unary
|
110
|
+
|
111
|
+
# Add a role value restriction
|
112
|
+
# REVISIT: Can add join-role-value-restrictions here, if we ever provide a way to define them
|
113
|
+
if references[-1].to_role && references[-1].to_role.role_value_restriction
|
114
|
+
restrictions << references[-1].to_role.role_value_restriction
|
115
|
+
end
|
116
|
+
|
117
|
+
vt = references[-1].is_self_value ? references[-1].from : references[-1].to
|
118
|
+
params[:length] ||= vt.length if vt.length.to_i != 0
|
119
|
+
params[:scale] ||= vt.scale if vt.scale.to_i != 0
|
120
|
+
while vt.supertype
|
121
|
+
params[:length] ||= vt.length if vt.length.to_i != 0
|
122
|
+
params[:scale] ||= vt.scale if vt.scale.to_i != 0
|
123
|
+
restrictions << vt.value_restriction if vt.value_restriction
|
124
|
+
vt = vt.supertype
|
125
|
+
end
|
126
|
+
return [vt.name, params, restrictions]
|
127
|
+
end
|
128
|
+
|
129
|
+
def comment
|
130
|
+
@references.map do |ref|
|
131
|
+
(ref.is_mandatory ? "" : "maybe ") +
|
132
|
+
(ref.fact_type && ref.fact_type.entity_type ? ref.fact_type.entity_type.name+" is where " : "") +
|
133
|
+
ref.reading
|
134
|
+
end * " and "
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
"#{@references[0].from.name} column #{name('.')}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Reference
|
143
|
+
def columns(excluded_supertypes)
|
144
|
+
kind = ""
|
145
|
+
cols =
|
146
|
+
if is_unary
|
147
|
+
kind = "unary "
|
148
|
+
[Column.new()]
|
149
|
+
elsif is_self_value
|
150
|
+
kind = "self-role "
|
151
|
+
[Column.new()]
|
152
|
+
elsif is_simple_reference
|
153
|
+
@to.reference_columns(excluded_supertypes)
|
154
|
+
else
|
155
|
+
kind = "absorbing "
|
156
|
+
@to.all_columns(excluded_supertypes)
|
157
|
+
end
|
158
|
+
|
159
|
+
cols.each do |c|
|
160
|
+
c.prepend self
|
161
|
+
end
|
162
|
+
|
163
|
+
debug :columns, "Columns from #{kind}#{self}" do
|
164
|
+
cols.each {|c|
|
165
|
+
debug :columns, "#{c}"
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Concept
|
172
|
+
attr_accessor :columns
|
173
|
+
|
174
|
+
def populate_columns
|
175
|
+
@columns = all_columns({})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class ValueType
|
180
|
+
def identifier_columns
|
181
|
+
debug :columns, "Identifier Columns for #{name}" do
|
182
|
+
raise "Illegal call to identifier_columns for absorbed ValueType #{name}" unless is_table
|
183
|
+
columns.select{|column| column.references[0] == self_value_reference}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def reference_columns(excluded_supertypes)
|
188
|
+
debug :columns, "Reference Columns for #{name}" do
|
189
|
+
if is_table
|
190
|
+
[Column.new(self_value_reference)]
|
191
|
+
else
|
192
|
+
[Column.new]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def all_columns(excluded_supertypes)
|
198
|
+
columns = []
|
199
|
+
debug :columns, "All Columns for #{name}" do
|
200
|
+
if is_table
|
201
|
+
self_value_reference
|
202
|
+
else
|
203
|
+
columns << Column.new
|
204
|
+
end
|
205
|
+
references_from.each do |ref|
|
206
|
+
debug :columns, "Columns absorbed via #{ref}" do
|
207
|
+
columns += ref.columns({})
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
columns
|
212
|
+
end
|
213
|
+
|
214
|
+
def self_value_reference
|
215
|
+
# Make a reference for the self-value column
|
216
|
+
@self_value_reference ||= Reference.new(self, nil).tabulate
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class EntityType
|
221
|
+
def identifier_columns
|
222
|
+
debug :columns, "Identifier Columns for #{name}" do
|
223
|
+
if absorbed_via and
|
224
|
+
# If this is a subtype that has its own identification, use that.
|
225
|
+
(all_type_inheritance_by_subtype.size == 0 ||
|
226
|
+
all_type_inheritance_by_subtype.detect{|ti| ti.provides_identification })
|
227
|
+
return absorbed_via.from.identifier_columns
|
228
|
+
end
|
229
|
+
|
230
|
+
preferred_identifier.role_sequence.all_role_ref.map do |role_ref|
|
231
|
+
ref = references_from.detect {|ref| ref.to_role == role_ref.role}
|
232
|
+
|
233
|
+
columns.select{|column| column.references[0] == ref}
|
234
|
+
end.flatten
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def reference_columns(excluded_supertypes)
|
239
|
+
debug :columns, "Reference Columns for #{name}" do
|
240
|
+
|
241
|
+
if absorbed_via and
|
242
|
+
# If this is a subtype that has its own identification, use that.
|
243
|
+
(all_type_inheritance_by_subtype.size == 0 ||
|
244
|
+
all_type_inheritance_by_subtype.detect{|ti| ti.provides_identification })
|
245
|
+
return absorbed_via.from.reference_columns(excluded_supertypes)
|
246
|
+
end
|
247
|
+
|
248
|
+
# REVISIT: Should have built preferred_identifier_references
|
249
|
+
preferred_identifier.role_sequence.all_role_ref.map do |role_ref|
|
250
|
+
# REVISIT: Should index references by to_role:
|
251
|
+
ref = references_from.detect {|ref| ref.to_role == role_ref.role}
|
252
|
+
|
253
|
+
raise "reference for role #{role.describe} not found on #{name} in #{references_from.size} references:\n\t#{references_from.map(&:to_s)*"\n\t"}" unless ref
|
254
|
+
|
255
|
+
ref.columns({})
|
256
|
+
end.flatten
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def all_columns(excluded_supertypes)
|
261
|
+
debug :columns, "All Columns for #{name}" do
|
262
|
+
columns = []
|
263
|
+
sups = supertypes
|
264
|
+
references_from.sort_by do |ref|
|
265
|
+
# Put supertypes first, in order, then non-subtype references, then subtypes, otherwise retaining their order:
|
266
|
+
sups.index(ref.to) ||
|
267
|
+
(!ref.fact_type.is_a?(TypeInheritance) && references_from.size+references_from.index(ref)) ||
|
268
|
+
references_from.size*2+references_from.index(ref)
|
269
|
+
end.each do |ref|
|
270
|
+
debug :columns, "Columns absorbed via #{ref}" do
|
271
|
+
if (ref.role_type == :supertype)
|
272
|
+
if excluded_supertypes[ref.to]
|
273
|
+
debug :columns, "Exclude #{ref.to.name}, we already inherited it"
|
274
|
+
next
|
275
|
+
end
|
276
|
+
|
277
|
+
next if (ref.to.absorbed_via != ref)
|
278
|
+
excluded_supertypes[ref.to] = true
|
279
|
+
columns += ref.columns(excluded_supertypes)
|
280
|
+
else
|
281
|
+
columns += ref.columns({})
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
columns
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class Vocabulary
|
291
|
+
# Do things like adding ID fields and ValueType self-value columns
|
292
|
+
def finish_schema
|
293
|
+
all_feature.each do |feature|
|
294
|
+
feature.self_value_reference if feature.is_a?(ValueType) && feature.is_table
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def populate_all_columns
|
299
|
+
# REVISIT: Is now a good time to apply schema transforms or should this be more explicit?
|
300
|
+
finish_schema
|
301
|
+
|
302
|
+
debug :columns, "Populating all columns" do
|
303
|
+
all_feature.each do |feature|
|
304
|
+
next if !feature.is_a?(Concept) || !feature.is_table
|
305
|
+
debug :columns, "Populating columns for table #{feature.name}" do
|
306
|
+
feature.populate_columns
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
debug :columns, "Finished columns" do
|
311
|
+
all_feature.each do |feature|
|
312
|
+
next if !feature.is_a?(Concept) || !feature.is_table
|
313
|
+
debug :columns, "Finished columns for table #{feature.name}" do
|
314
|
+
feature.columns.each do |column|
|
315
|
+
debug :columns, "#{column}"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
end
|