activefacts 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/Manifest.txt +7 -2
  2. data/examples/CQL/Address.cql +0 -2
  3. data/examples/CQL/Blog.cql +2 -2
  4. data/examples/CQL/CompanyDirectorEmployee.cql +1 -1
  5. data/examples/CQL/Death.cql +1 -1
  6. data/examples/CQL/Metamodel.cql +5 -5
  7. data/examples/CQL/MultiInheritance.cql +2 -0
  8. data/examples/CQL/PersonPlaysGame.cql +1 -1
  9. data/lib/activefacts/cql/Concepts.treetop +17 -8
  10. data/lib/activefacts/cql/Language/English.treetop +1 -2
  11. data/lib/activefacts/generate/absorption.rb +1 -1
  12. data/lib/activefacts/generate/null.rb +8 -1
  13. data/lib/activefacts/generate/oo.rb +174 -0
  14. data/lib/activefacts/generate/ruby.rb +49 -208
  15. data/lib/activefacts/generate/sql/server.rb +137 -72
  16. data/lib/activefacts/generate/text.rb +1 -1
  17. data/lib/activefacts/input/orm.rb +12 -2
  18. data/lib/activefacts/persistence.rb +5 -1
  19. data/lib/activefacts/persistence/columns.rb +324 -0
  20. data/lib/activefacts/persistence/foreignkey.rb +87 -0
  21. data/lib/activefacts/persistence/index.rb +171 -0
  22. data/lib/activefacts/persistence/reference.rb +326 -0
  23. data/lib/activefacts/persistence/tables.rb +307 -0
  24. data/lib/activefacts/support.rb +1 -1
  25. data/lib/activefacts/version.rb +1 -1
  26. data/lib/activefacts/vocabulary/extensions.rb +42 -5
  27. data/spec/absorption_spec.rb +8 -6
  28. data/spec/cql_cql_spec.rb +1 -0
  29. data/spec/cql_sql_spec.rb +2 -1
  30. data/spec/cql_unit_spec.rb +0 -6
  31. data/spec/norma_cql_spec.rb +1 -0
  32. data/spec/norma_sql_spec.rb +1 -1
  33. data/spec/norma_tables_spec.rb +41 -43
  34. metadata +9 -4
  35. 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 a ValueType definition for the passed role reference
62
- def sql_type(role_ref)
63
- if role_ref.role.fact_type.all_role.size == 1
64
- "bit"
65
- else
66
- vt = role_ref.role.concept
67
- length = vt.length
68
- scale = vt.scale
69
- while vt.supertype
70
- length ||= vt.length
71
- scale ||= vt.scale
72
- vt = vt.supertype
73
- end
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
- when "VariableLengthText"; "varchar"
88
- when "Decimal"; "decimal"
89
- else vt.name
90
- end
91
- if length && length != 0
92
- basic_type + ((scale && scale != 0) ? "(#{length}, #{scale})" : "(#{length})")
93
- else
94
- basic_type
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
- end +
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.absorbed_reference_roles.all_role_ref
119
- pk_names = pk.map{|rr| column_name(rr) }
111
+ pk = table.identifier_columns
112
+ identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
120
113
 
121
- columns = table.absorbed_roles.all_role_ref.sort_by do |role_ref|
122
- name = column_name(role_ref)
123
- [pk_names.include?(name) ? 0 : 1, name]
124
- end.map do |role_ref|
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
- pk_def =
129
- if pk.detect{ |role_ref| !role_ref.role.is_mandatory }
130
- # Any nullable fields mean this can't be a primary key, just a unique constraint
131
- "\tUNIQUE("
132
- else
133
- "\tPRIMARY KEY("
134
- end +
135
- table.absorbed_reference_roles.all_role_ref.map do |role_ref|
136
- column_name(role_ref)
137
- end*", " + ")"
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.absorbed_references.sort_by { |role, other_table, from_columns, to_columns|
141
- [ other_table.name, from_columns.map{|c| column_name(c)} ]
142
- }.each do |role, other_table, from_columns, to_columns|
143
- fk =
144
- if tables_emitted[other_table] && !@delay_fks
145
- inline_fks << "\t"
146
- else
147
- delayed_foreign_keys << "ALTER TABLE #{escape table.name}\n\tADD "
148
- end.last
149
- fk << "FOREIGN KEY(#{from_columns.map{|c| column_name(c)}*", "})\n"+
150
- "\tREFERENCES #{escape other_table.name}(#{to_columns.map{|c| column_name(c)}*", "})"
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
- puts((columns + [pk_def] + inline_fks)*",\n")
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 = min =~ /[^0-9\.]/ ? q+min+q : min.to_i
165
- max = max =~ /[^0-9\.]/ ? q+max+q : max.to_i
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,
@@ -1 +1,5 @@
1
- require 'activefacts/persistence/composition'
1
+ require 'activefacts/persistence/reference'
2
+ require 'activefacts/persistence/tables'
3
+ require 'activefacts/persistence/columns'
4
+ require 'activefacts/persistence/foreignkey'
5
+ require 'activefacts/persistence/index'
@@ -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