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
@@ -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
|