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