activefacts-compositions 1.9.17 → 1.9.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/activefacts-compositions.gemspec +2 -2
  3. data/lib/activefacts/compositions/binary.rb +1 -1
  4. data/lib/activefacts/compositions/compositor.rb +16 -12
  5. data/lib/activefacts/compositions/datavault.rb +110 -115
  6. data/lib/activefacts/compositions/relational.rb +137 -94
  7. data/lib/activefacts/compositions/staging.rb +89 -27
  8. data/lib/activefacts/compositions/traits/datavault.rb +116 -49
  9. data/lib/activefacts/compositions/traits/rails.rb +2 -2
  10. data/lib/activefacts/compositions/version.rb +1 -1
  11. data/lib/activefacts/generator/doc/cwm.rb +6 -18
  12. data/lib/activefacts/generator/doc/ldm.rb +1 -1
  13. data/lib/activefacts/generator/etl/unidex.rb +341 -0
  14. data/lib/activefacts/generator/oo.rb +31 -14
  15. data/lib/activefacts/generator/rails/models.rb +6 -5
  16. data/lib/activefacts/generator/rails/schema.rb +5 -9
  17. data/lib/activefacts/generator/ruby.rb +2 -2
  18. data/lib/activefacts/generator/sql/mysql.rb +3 -184
  19. data/lib/activefacts/generator/sql/oracle.rb +3 -152
  20. data/lib/activefacts/generator/sql/postgres.rb +3 -145
  21. data/lib/activefacts/generator/sql/server.rb +3 -126
  22. data/lib/activefacts/generator/sql.rb +54 -422
  23. data/lib/activefacts/generator/summary.rb +15 -6
  24. data/lib/activefacts/generator/traits/expr.rb +41 -0
  25. data/lib/activefacts/generator/traits/sql/mysql.rb +280 -0
  26. data/lib/activefacts/generator/traits/sql/oracle.rb +265 -0
  27. data/lib/activefacts/generator/traits/sql/postgres.rb +287 -0
  28. data/lib/activefacts/generator/traits/sql/server.rb +262 -0
  29. data/lib/activefacts/generator/traits/sql.rb +538 -0
  30. metadata +13 -8
  31. data/lib/activefacts/compositions/docgraph.rb +0 -798
  32. data/lib/activefacts/compositions/staging/persistent.rb +0 -107
@@ -0,0 +1,341 @@
1
+ #
2
+ # ActiveFacts Unified Index Schema Generator
3
+ #
4
+ # Copyright (c) 2009-2016 Clifford Heath. Read the LICENSE file.
5
+ #
6
+ require 'digest/sha1'
7
+ require 'activefacts/metamodel'
8
+ require 'activefacts/metamodel/datatypes'
9
+ require 'activefacts/compositions'
10
+ require 'activefacts/compositions/names'
11
+ require 'activefacts/generator'
12
+ require 'activefacts/generator/traits/sql'
13
+
14
+ module ActiveFacts
15
+ module Generators
16
+ module ETL
17
+ class Unidex
18
+
19
+ MM = ActiveFacts::Metamodel unless const_defined?(:MM)
20
+ def self.options
21
+ # REVISIT: There's no way to support SQL dialect options here
22
+ sql_trait = ActiveFacts::Generators::Traits::SQL
23
+ Class.new.extend(sql_trait). # Anonymous class to enable access to traits module instance methods
24
+ options.
25
+ merge(
26
+ {
27
+ dialect: [String, "SQL Dialect to use"],
28
+ value_width: [Integer, "Number of characters to index from long values"],
29
+ phonetic_confidence: [Integer, "Percentage confidence for a phonetic match"],
30
+ }
31
+ )
32
+ end
33
+
34
+ def initialize composition, options = {}
35
+ @composition = composition
36
+ @options = options
37
+
38
+ @trait = ActiveFacts::Generators::Traits::SQL
39
+ if @dialect = options.delete("dialect")
40
+ require 'activefacts/generator/traits/sql/'+@dialect
41
+ trait_name = ActiveFacts::Generators::Traits::SQL.constants.detect{|c| c.to_s =~ %r{#{@dialect}}i}
42
+ @trait = @trait.const_get(trait_name)
43
+ end
44
+ self.class.include @trait
45
+ self.class.extend @trait
46
+ extend @trait
47
+
48
+ process_options options
49
+ end
50
+
51
+ def process_options options
52
+ @value_width = (options.delete('value_width') || 32).to_i
53
+ @phonetic_confidence = (options.delete('phonetic_confidence') || 70).to_i
54
+
55
+ super
56
+ end
57
+
58
+ def generate
59
+ @all_table_unions = []
60
+ header +
61
+ @composition.
62
+ all_composite.
63
+ sort_by{|c| c.mapping.name}.
64
+ map{|c| generate_composite c}.
65
+ concat([all_union(@all_table_unions)]).
66
+ compact*"\n" +
67
+ trailer
68
+ end
69
+
70
+ def all_union unions
71
+ return '' if unions.empty?
72
+ create_or_replace("#{schema_name}_unidex", 'VIEW') + " AS\n" +
73
+ unions.compact.map{|s| "SELECT * FROM "+s } *
74
+ "\nUNION ALL " +
75
+ ";\n"
76
+ end
77
+
78
+ def header
79
+ schema_prefix
80
+ end
81
+
82
+ def generate_composite composite
83
+ return nil if composite.mapping.injection_annotation
84
+ return nil if composite.mapping.object_type.is_static
85
+
86
+ trace :unidex, "Generating view for #{table_name(composite)}" do
87
+ union =
88
+ composite.mapping.all_member.to_a.sort_by{|m| m.name}.flat_map do |member|
89
+ next nil if member.injection_annotation
90
+ rank_key = member.rank_key
91
+
92
+ case key_type = rank_key[0]
93
+ when MM::Component::RANK_SURROGATE, # A surrogate key; these do not get indexed
94
+ MM::Component::RANK_DISCRIMINATOR, # Replacement for exclusive indicators, often subtypes
95
+ MM::Component::RANK_MULTIPLE # Nested absorption
96
+ trace :unidex, "Ignoring #{MM::DataTypes::TypeNames[key_type]} #{column_name member}"
97
+ next nil
98
+ when MM::Component::RANK_INJECTION # ValueField (index), ValidFrom (don't) or an Absorption with an injection annotation
99
+ if MM::ValueField === member
100
+ generate_value leaf
101
+ else
102
+ nil
103
+ end
104
+ when MM::Component::RANK_INDICATOR
105
+ generate_indicator member
106
+ else
107
+ raise "Unexpected non-Absorption" unless MM::Absorption === member
108
+ if member.foreign_key
109
+ # Index this record by the natural key of the FK target record, if possible
110
+ generate_joined_value member
111
+ # elsif member.full_absorption # REVISIT: Anything special to do here?
112
+ else
113
+ (member.all_member.size > 0 ? member.all_leaf : [member]).flat_map do |leaf|
114
+ generate_value leaf
115
+ end
116
+ end
117
+ end
118
+ end.compact * "\nUNION ALL"
119
+
120
+ if union.size > 0
121
+ union_name = "#{table_name(composite)}_unidex"
122
+ @all_table_unions << union_name
123
+
124
+ "/*\n"+
125
+ " * View to extract unified index values for #{table_name(composite)}\n"+
126
+ " */\n"+
127
+ create_or_replace("#{union_name}", 'VIEW') + " AS" +
128
+ union +
129
+ ";\n"
130
+ else
131
+ ''
132
+ end
133
+ end
134
+ end
135
+
136
+ def trailer
137
+ ''
138
+ end
139
+
140
+ def generate_indicator leaf
141
+ nil # REVISIT: Do we need anything here?
142
+ # select leaf.root, safe_column_name(leaf), 1, column_name(leaf), 1
143
+ end
144
+
145
+ # This foreign key connects two composites (tables)
146
+ def generate_joined_value member
147
+ foreign_key = member.foreign_key
148
+ # REVISIT: Is this restriction even necessary?
149
+ return nil unless foreign_key.composite.mapping.object_type.is_static
150
+
151
+ # Index the source table by the natural key of the target, if we can find one
152
+ indices = foreign_key.composite.all_index
153
+ return null if indices.empty?
154
+
155
+ search_index_by = {}
156
+ searchable_indices =
157
+ indices.select do |ix|
158
+ next false if !ix.is_unique
159
+ non_fk_components = ix.all_index_field.map(&:component) - foreign_key.all_index_field.map(&:component)
160
+ next unless non_fk_components.size == 1
161
+ component = non_fk_components[0]
162
+ next unless MM::Absorption === component
163
+ value_type = component.object_type
164
+ search_methods = value_type.applicable_parameter_restrictions('Search')
165
+ search_methods.reject!{|vtpr| m = vtpr.value_range.minimum_bound and m.value == 'none'}
166
+ search_methods.map!{|sm| sm.value_range.minimum_bound.value.effective_value}
167
+ if search_methods.empty?
168
+ false
169
+ else
170
+ search_index_by[ix] = search_methods
171
+ end
172
+ end
173
+ return nil if search_index_by.empty?
174
+
175
+ search_index_by.flat_map do |search_index, search_methods|
176
+ trace :unidex, "Search #{table_name foreign_key.source_composite} via #{table_name search_index.composite}.#{column_name search_index.all_index_field.to_a[0].component} using #{search_methods.map(&:inspect)*', '}"
177
+
178
+ fk_pairs =
179
+ foreign_key.all_foreign_key_field.to_a.
180
+ zip foreign_key.all_index_field.to_a
181
+ leaf = search_index.all_index_field.to_a[0].component # Returning this natural index value
182
+ source_table = table_name(foreign_key.composite)
183
+ source_field = safe_column_name(member)
184
+ type_name, options = leaf.data_type(data_type_context) # Which has this type_name
185
+ intrinsic_type = MM::DataType.intrinsic_type(type_name) # Which corresponds to this intrinsic type
186
+
187
+ col_expr = Expression.new(
188
+ %Q{
189
+ (SELECT #{safe_column_name(leaf)}
190
+ FROM #{source_table} AS f
191
+ WHERE #{
192
+ fk_pairs.map do |fkf, ixf|
193
+ "#{table_name foreign_key.source_composite}.#{safe_column_name(fkf.component)} = f.#{safe_column_name(ixf.component)}"
194
+ end*' AND '
195
+ })}.
196
+ gsub(/\s+/,' '),
197
+ intrinsic_type,
198
+ foreign_key.all_foreign_key_field.to_a.all?{|fkf| fkf.component.path_mandatory}
199
+ )
200
+ search_expr foreign_key.source_composite, intrinsic_type, col_expr, search_methods, source_field
201
+ end
202
+ end
203
+
204
+ def generate_value leaf
205
+ return nil unless leaf.is_a?(MM::Absorption)
206
+
207
+ value_type = leaf.object_type
208
+ type_name, options = leaf.data_type(data_type_context)
209
+ length = options[:length]
210
+ value_constraint = options[:value_constraint]
211
+
212
+ # Look for instructions on how to index this leaf for search:
213
+ search_methods = value_type.applicable_parameter_restrictions('Search')
214
+ search_methods.reject!{|vtpr| m = vtpr.value_range.minimum_bound and m.value == 'none'}
215
+ return nil if search_methods.empty?
216
+ search_methods.map!{|sm| sm.value_range.minimum_bound.value.effective_value}
217
+
218
+ # Convert from the model's data type to a metamodel type, if possible
219
+ intrinsic_type = MM::DataType.intrinsic_type(type_name)
220
+ data_type_name = intrinsic_type ? MM::DataType::TypeNames[intrinsic_type] : type_name
221
+ trace :unidex, "Search #{table_name leaf.root}.#{column_name(leaf)} as #{data_type_name} using #{search_methods.map(&:inspect)*', '}"
222
+
223
+ col_expr = Expression.new(safe_column_name(leaf), intrinsic_type, leaf.is_mandatory)
224
+ source_field = safe_column_name(leaf)
225
+
226
+ search_expr leaf.root, intrinsic_type, col_expr, search_methods, source_field
227
+ end
228
+
229
+ def search_expr composite, intrinsic_type, col_expr, search_methods, source_field
230
+ case intrinsic_type
231
+ when MM::DataType::TYPE_Char,
232
+ MM::DataType::TYPE_String,
233
+ MM::DataType::TYPE_Text
234
+ # Produce a truncated value with the requested search
235
+ search_methods.flat_map do |sm|
236
+ case sm
237
+ when 'none' # Do not index this value
238
+ nil
239
+ when 'simple' # Disregard white-space only
240
+ select(composite, truncate(col_expr, @value_width), 'simple', source_field, 1.0)
241
+
242
+ when 'alpha' # Strip white space and punctuation, just use alphabetic characters
243
+ select(composite, truncate(as_alpha(col_expr), @value_width), 'alpha', source_field, 0.9)
244
+
245
+ when 'words' # Break the text into words and match each word like alpha
246
+ nil # REVISIT: Implement this type
247
+
248
+ # when 'phrases' # Words, but where adjacent sequences of words matter
249
+ when 'typo' # Use trigram similarity to detect typographic errors
250
+ # REVISIT: Implement this type properly
251
+ select(composite, trigram(as_alpha(col_expr)), 'typo', source_field, 0.9)
252
+
253
+ when 'phonetic' # Use phonetic matching as well as trigrams
254
+ phonetics(col_expr).map do |p|
255
+ select(composite, p, 'phonetic', source_field, @phonetic_confidence/100.0)
256
+ end
257
+
258
+ when 'names' # Break the text into words and match each word like phonetic
259
+ nil # REVISIT: Implement this type
260
+
261
+ when 'text' # Index a large text field using significant words and phrases
262
+ nil # REVISIT: Implement this type
263
+ else
264
+ raise "Unknown search method #{sm}"
265
+ end
266
+ end
267
+
268
+ when MM::DataType::TYPE_Boolean
269
+ nil # REVISIT: Implement this type
270
+
271
+ when MM::DataType::TYPE_Integer,
272
+ MM::DataType::TYPE_Real,
273
+ MM::DataType::TYPE_Decimal,
274
+ MM::DataType::TYPE_Money
275
+ # Produce a right-justified value
276
+ select(composite, lexical_decimal(col_expr, @value_width, value_type.scale), 'simple', source_field, 1)
277
+
278
+ when MM::DataType::TYPE_Date
279
+ # Produce an ISO representation that sorts lexically (YYYY-MM-DD)
280
+ # REVISIT: Support search methods here
281
+ select(composite, lexical_date(col_expr), 'simple', source_field, 1)
282
+
283
+ when MM::DataType::TYPE_DateTime,
284
+ MM::DataType::TYPE_Timestamp
285
+ # Produce an ISO representation that sorts lexically (YYYY-MM-DD HH:mm:ss)
286
+ # REVISIT: Support search methods here
287
+ select(composite, lexical_datetime(col_expr), 'simple', source_field, 1)
288
+
289
+ when MM::DataType::TYPE_Time
290
+ # Produce an ISO representation that sorts lexically (YYYY-MM-DD HH:mm:ss)
291
+ select(composite, lexical_time(col_expr), 'simple', source_field, 1)
292
+
293
+ when MM::DataType::TYPE_Binary
294
+ nil # No indexing applied
295
+ when nil # Data Type is unknown
296
+ else
297
+ end
298
+ end
299
+
300
+ def stylise_column_name name
301
+ name.words.send(@column_case)*@column_joiner
302
+ end
303
+
304
+ def select composite, expression, processing, source_field, confidence = 1, where = []
305
+ # These fields are in order of index precedence, to co-locate
306
+ # comparable values regardless of source record type or column
307
+ where << 'Value IS NOT NULL' if expression.to_s =~ /\bNULL\b/
308
+ processing_name = stylise_column_name("Processing")
309
+ value_name = stylise_column_name("Value")
310
+ load_batch_id_name = stylise_column_name("LoadBatchID")
311
+ record_guid_name = stylise_column_name("RecordGUID")
312
+ confidence_name = stylise_column_name("Confidence")
313
+ source_table_name = stylise_column_name("SourceTable")
314
+ source_field_name = stylise_column_name("SourceField")
315
+ expression_text = expression.to_s
316
+ expression_text = "ARRAY[#{expression_text}]" unless expression.is_array
317
+ select = %Q{
318
+ SELECT '#{processing}' AS #{processing_name},
319
+ #{expression_text} AS #{value_name},
320
+ #{load_batch_id_name},
321
+ #{"%.2f" % confidence} AS #{confidence_name},
322
+ #{record_guid_name},
323
+ '#{safe_table_name(composite)}' AS #{source_table_name},
324
+ '#{source_field}' AS #{source_field_name}
325
+ FROM #{safe_table_name(composite)}
326
+ WHERE COALESCE(#{expression},'') <> ''}.
327
+ unindent
328
+
329
+ if where.empty?
330
+ select
331
+ else
332
+ "\nSELECT * FROM (#{select}\n) AS s WHERE #{where*' AND '}"
333
+ end
334
+
335
+ end
336
+
337
+ end
338
+ end
339
+ publish_generator ETL::Unidex
340
+ end
341
+ end
@@ -25,7 +25,8 @@ module ActiveFacts
25
25
  end
26
26
 
27
27
  def generate
28
- @composites_emitted = {}
28
+ @composites_started = {}
29
+ @composites_finished = {}
29
30
 
30
31
  retract_intrinsic_types
31
32
 
@@ -58,7 +59,7 @@ module ActiveFacts
58
59
  return true if o.name == "_ImplicitBooleanValueType"
59
60
  return false if o.supertype
60
61
  # A value type with no supertype must be emitted if it is the child in any absorption:
61
- return !composite.mapping.all_member.detect{|m| m.forward_absorption}
62
+ return !composite.mapping.all_member.detect{|m| m.forward_mapping}
62
63
  end
63
64
 
64
65
  def retract_intrinsic_types
@@ -92,8 +93,25 @@ module ActiveFacts
92
93
  "REVISIT: override class_finale\n"
93
94
  end
94
95
 
95
- def generate_class composite, predefine_role_players = true
96
- return nil if @composites_emitted[composite]
96
+ def exclude_as_counterpart member
97
+ # If emitting a role also creates its counterpart, we can exclude the counterparts.
98
+ # This depends on the subtype, so centralise the knowledge here where it can be overridden.
99
+ # Non-Mappings always get emitted:
100
+ return false unless MM::Mapping === member
101
+
102
+ # TypeInheritance always gets skipped:
103
+ return true if MM::Absorption === member && member.child_role.fact_type.is_a?(MM::TypeInheritance)
104
+
105
+ # Don't skip the mandatory counterpart of a one-to-one:
106
+ return false if member.is_one_to_one and member.is_mandatory
107
+
108
+ # Otherwise skip all reverse mappings (these have a forward mapping)
109
+ return member.forward_mapping
110
+ end
111
+
112
+ def generate_class composite, precursor_to = [], predefine_role_players = true
113
+ return nil if @composites_started[composite]
114
+ @composites_started[composite] = true
97
115
 
98
116
  mapping = composite.mapping
99
117
  object_type = mapping.object_type
@@ -104,9 +122,7 @@ module ActiveFacts
104
122
  supertype_composites =
105
123
  object_type.all_supertype.map{|s| composite_for(s) }.compact
106
124
  forward_declarations +=
107
- supertype_composites.map{|c| generate_class(c, false)}.compact
108
-
109
- @composites_emitted[composite] = true
125
+ supertype_composites.map{|c| generate_class(c, precursor_to+[composite], false)}.compact
110
126
 
111
127
  # Select the members that will be declared as O-O roles:
112
128
  mapping.re_rank
@@ -114,23 +130,22 @@ module ActiveFacts
114
130
  all_member.
115
131
  sort_by{|m| m.ordinal}.
116
132
  reject do |m|
117
- m.is_a?(MM::Absorption) and
118
- m.forward_absorption || m.child_role.fact_type.is_a?(MM::TypeInheritance)
133
+ exclude_as_counterpart(m)
119
134
  end
120
135
 
121
136
  if predefine_role_players
122
- # The idea was good, but we need to avoid triggering a forward reference problem.
123
- # We only do it when we're not dumping a supertype dependency.
124
- #
125
- # For those roles that derive from Mappings, produce class definitions to avoid forward references:
137
+ # To reduce forward references, emit any classes that play a role
138
+ # we're about to emit, unless one is a subtype of any class that
139
+ # is currently being processed.
126
140
  forward_composites =
127
141
  members.
128
142
  select{ |m| m.is_a?(MM::Mapping) }.
129
143
  map{ |m| composite_for m.object_type }.
144
+ reject{|c| precursor_to.detect{|p| c.mapping.object_type.supertypes_transitive.include?(p.mapping.object_type) }}.
130
145
  compact.
131
146
  sort_by{|c| c.mapping.name}
132
147
  forward_declarations +=
133
- forward_composites.map{|c| generate_class(c)}.compact
148
+ forward_composites.map{|c| generate_class(c, precursor_to+[composite])}.compact
134
149
  end
135
150
 
136
151
  forward_declarations = forward_declarations.map{|f| "#{f}\n"}*''
@@ -166,6 +181,8 @@ module ActiveFacts
166
181
  value_type_declaration object_type
167
182
  end
168
183
 
184
+ @composites_finished[composite] = true
185
+
169
186
  forward_declarations +
170
187
  class_prelude(object_type, primary_supertype) +
171
188
  type_declaration +
@@ -172,9 +172,9 @@ module ActiveFacts
172
172
  end
173
173
 
174
174
  [
175
- fk.absorption ? " \# #{fk.absorption.comment}" : nil,
175
+ fk.mapping ? " \# #{fk.mapping.comment}" : nil,
176
176
  " belongs_to :#{association_name}#{class_name}#{foreign_key}",
177
- fk.absorption ? '' : nil,
177
+ fk.mapping ? '' : nil,
178
178
  ]
179
179
  end.compact
180
180
  end
@@ -193,13 +193,13 @@ module ActiveFacts
193
193
 
194
194
  [
195
195
  # REVISIT: We want the reverse-order comment here really
196
- fk.absorption ? " \# #{fk.absorption.comment}" : nil,
196
+ fk.mapping ? " \# #{fk.mapping.comment}" : nil,
197
197
  %Q{ #{association_type} :#{association_name}} +
198
198
  %Q{, :class_name => '#{fk.source_composite.rails.class_name}'} +
199
199
  %Q{, :foreign_key => :#{fk.all_foreign_key_field.single.component.column_name.snakecase}} +
200
200
  %Q{, :dependent => :destroy}
201
201
  ] +
202
- # If fk.absorption.source_composite is a join table, we can emit a has_many :through for each other key
202
+ # If fk.mapping.source_composite is a join table, we can emit a has_many :through for each other key
203
203
  # REVISIT: We could alternately do this for all belongs_to's in the source composite
204
204
  if fk.source_composite.primary_index.all_index_field.size > 1
205
205
  fk.source_composite.primary_index.all_index_field.map(&:component).flat_map do |ic|
@@ -212,7 +212,7 @@ module ActiveFacts
212
212
  else
213
213
  []
214
214
  end +
215
- [fk.absorption ? '' : nil]
215
+ [fk.mapping ? '' : nil]
216
216
  end
217
217
  end
218
218
 
@@ -221,6 +221,7 @@ module ActiveFacts
221
221
  ccs =
222
222
  composite.mapping.all_leaf.flat_map do |component|
223
223
  next unless component.path_mandatory && !component.is_a?(Metamodel::Indicator)
224
+ next if composite.primary_index != composite.natural_index && composite.primary_index.all_index_field.detect{|ixf| ixf.component == component}
224
225
  next if component.is_a?(Metamodel::Mapping) && component.object_type.is_a?(Metamodel::ValueType) && component.is_auto_assigned
225
226
  [ " validates :#{component.column_name.snakecase}, :presence => true" ]
226
227
  end.compact
@@ -18,7 +18,7 @@ module ActiveFacts
18
18
  HEADER = "# Auto-generated from CQL, edits will be lost"
19
19
  def self.options
20
20
  ({
21
- exclude_fks: ['Boolean', "Don't generate foreign key definitions"],
21
+ fks: ['Boolean', "Generate foreign key definitions"],
22
22
  include_comments: ['Boolean', "Generate a comment for each column showing the absorption path"],
23
23
  closed_world: ['Boolean', "Set this if your DBMS only allows one null in a unique index (MS SQL)"],
24
24
  })
@@ -27,7 +27,7 @@ module ActiveFacts
27
27
  def initialize composition, options = {}
28
28
  @composition = composition
29
29
  @options = options
30
- @option_exclude_fks = options.delete("exclude_fks")
30
+ @option_exclude_fks = [false, 'f', 'n', 'no'].include?(options.delete("fks"))
31
31
  @option_include_comments = options.delete("include_comments")
32
32
  @option_closed_world = options.delete("closed_world")
33
33
  end
@@ -147,7 +147,7 @@ module ActiveFacts
147
147
  ixf.ordinal == 0 # It's first in this index
148
148
  end
149
149
  already_indexed = from_index_needed.any?{|ixf| @indexes_generated[ixf.access_path]}
150
- is_one_to_one = fk.absorption && fk.absorption.child_role.is_unique
150
+ is_one_to_one = fk.mapping && fk.mapping.child_role.is_unique
151
151
 
152
152
  index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{from_column_names[0]}")
153
153
  [
@@ -244,14 +244,10 @@ module ActiveFacts
244
244
  end
245
245
 
246
246
  def surrogate_type
247
- type_name, = choose_integer_type(0, 2**(default_surrogate_length-1)-1)
247
+ type_name, = choose_integer_range(0, 2**(default_surrogate_length-1)-1)
248
248
  type_name
249
249
  end
250
250
 
251
- def valid_from_type
252
- date_time_type
253
- end
254
-
255
251
  def date_time_type
256
252
  'datetime'
257
253
  end
@@ -271,7 +267,7 @@ module ActiveFacts
271
267
 
272
268
  # Return SQL type and (modified?) length for the passed base type
273
269
  def normalise_type type_name
274
- type = MM::DataType.normalise(type_name)
270
+ type = MM::DataType.intrinsic_type(type_name)
275
271
 
276
272
  [
277
273
  type,
@@ -69,7 +69,7 @@ module ActiveFacts
69
69
  if component.is_a?(MM::Absorption) and
70
70
  counterpart = component.object_type and
71
71
  counterpart_composite = composite_for(counterpart)
72
- counterpart_class_emitted = @composites_emitted[counterpart_composite]
72
+ counterpart_class_emitted = @composites_finished[counterpart_composite]
73
73
 
74
74
  counterpart_class_name = ruby_class_name counterpart_composite
75
75
  counterpart_default_role = ruby_role_name counterpart_composite.mapping
@@ -79,7 +79,7 @@ module ActiveFacts
79
79
 
80
80
  # Does the reverse role need explicit specification?
81
81
  implied_reverse_role_name = ruby_role_name(component.root.mapping)
82
- actual_reverse_role_name = ruby_role_name component.reverse_absorption
82
+ actual_reverse_role_name = ruby_role_name(component.reverse_mapping || component.forward_mapping)
83
83
 
84
84
  if implied_reverse_role_name != actual_reverse_role_name
85
85
  counterpart_spec = ", counterpart: :#{actual_reverse_role_name}"