activefacts 0.8.16 → 0.8.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Manifest.txt +10 -4
- data/bin/afgen +26 -20
- data/bin/cql +1 -1
- data/css/orm2.css +89 -9
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Genealogy.cql +5 -5
- data/examples/CQL/Metamodel.cql +121 -91
- data/examples/CQL/MonthInSeason.cql +2 -6
- data/examples/CQL/SeparateSubtype.cql +11 -9
- data/examples/CQL/ServiceDirector.cql +21 -33
- data/examples/CQL/Supervision.cql +0 -3
- data/examples/CQL/WindowInRoomInBldg.cql +10 -4
- data/examples/CQL/unit.cql +1 -1
- data/lib/activefacts.rb +1 -0
- data/lib/activefacts/cql/CQLParser.treetop +5 -1
- data/lib/activefacts/cql/Context.treetop +2 -7
- data/lib/activefacts/cql/Expressions.treetop +2 -2
- data/lib/activefacts/cql/FactTypes.treetop +37 -31
- data/lib/activefacts/cql/Language/English.treetop +21 -4
- data/lib/activefacts/cql/LexicalRules.treetop +59 -1
- data/lib/activefacts/cql/ObjectTypes.treetop +22 -12
- data/lib/activefacts/cql/Terms.treetop +13 -9
- data/lib/activefacts/cql/ValueTypes.treetop +30 -11
- data/lib/activefacts/cql/compiler.rb +34 -5
- data/lib/activefacts/cql/compiler/clause.rb +207 -116
- data/lib/activefacts/cql/compiler/constraint.rb +129 -105
- data/lib/activefacts/cql/compiler/entity_type.rb +49 -27
- data/lib/activefacts/cql/compiler/expression.rb +71 -42
- data/lib/activefacts/cql/compiler/fact.rb +70 -64
- data/lib/activefacts/cql/compiler/fact_type.rb +108 -57
- data/lib/activefacts/cql/compiler/query.rb +178 -0
- data/lib/activefacts/cql/compiler/shared.rb +13 -12
- data/lib/activefacts/cql/compiler/value_type.rb +10 -4
- data/lib/activefacts/cql/nodes.rb +1 -1
- data/lib/activefacts/cql/parser.rb +6 -2
- data/lib/activefacts/generate/absorption.rb +6 -3
- data/lib/activefacts/generate/cql.rb +140 -84
- data/lib/activefacts/generate/dm.rb +12 -6
- data/lib/activefacts/generate/help.rb +25 -6
- data/lib/activefacts/generate/helpers/oo.rb +195 -0
- data/lib/activefacts/generate/helpers/ordered.rb +589 -0
- data/lib/activefacts/generate/helpers/rails.rb +57 -0
- data/lib/activefacts/generate/html/glossary.rb +274 -54
- data/lib/activefacts/generate/json.rb +25 -22
- data/lib/activefacts/generate/null.rb +1 -0
- data/lib/activefacts/generate/rails/models.rb +244 -0
- data/lib/activefacts/generate/rails/schema.rb +185 -0
- data/lib/activefacts/generate/records.rb +1 -0
- data/lib/activefacts/generate/ruby.rb +51 -30
- data/lib/activefacts/generate/sql/mysql.rb +5 -3
- data/lib/activefacts/generate/sql/server.rb +8 -4
- data/lib/activefacts/generate/text.rb +1 -0
- data/lib/activefacts/generate/transform/surrogate.rb +209 -0
- data/lib/activefacts/generate/version.rb +1 -0
- data/lib/activefacts/input/orm.rb +234 -181
- data/lib/activefacts/mapping/rails.rb +122 -0
- data/lib/activefacts/persistence/columns.rb +34 -18
- data/lib/activefacts/persistence/foreignkey.rb +129 -71
- data/lib/activefacts/persistence/index.rb +42 -12
- data/lib/activefacts/persistence/reference.rb +37 -23
- data/lib/activefacts/persistence/tables.rb +53 -19
- data/lib/activefacts/registry.rb +11 -0
- data/lib/activefacts/support.rb +28 -10
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +246 -117
- data/lib/activefacts/vocabulary/metamodel.rb +105 -65
- data/lib/activefacts/vocabulary/verbaliser.rb +226 -194
- data/spec/absorption_spec.rb +1 -0
- data/spec/cql/comparison_spec.rb +8 -8
- data/spec/cql/contractions_spec.rb +16 -43
- data/spec/cql/entity_type_spec.rb +2 -1
- data/spec/cql/expressions_spec.rb +2 -2
- data/spec/cql/fact_type_matching_spec.rb +4 -1
- data/spec/cql/parser/bad_literals_spec.rb +30 -30
- data/spec/cql/parser/entity_types_spec.rb +6 -6
- data/spec/cql/parser/expressions_spec.rb +25 -19
- data/spec/cql/samples_spec.rb +5 -4
- data/spec/cql_cql_spec.rb +2 -1
- data/spec/cql_dm_spec.rb +4 -0
- data/spec/cql_mysql_spec.rb +4 -0
- data/spec/cql_parse_spec.rb +2 -0
- data/spec/cql_ruby_spec.rb +4 -0
- data/spec/cql_sql_spec.rb +4 -0
- data/spec/cqldump_spec.rb +7 -4
- data/spec/helpers/parse_to_ast_matcher.rb +7 -3
- data/spec/helpers/test_parser.rb +2 -0
- data/spec/norma_cql_spec.rb +5 -2
- data/spec/norma_ruby_spec.rb +4 -1
- data/spec/norma_ruby_sql_spec.rb +4 -1
- data/spec/norma_sql_spec.rb +4 -1
- data/spec/norma_tables_spec.rb +2 -2
- data/spec/ruby_api_spec.rb +1 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/transform_surrogate_spec.rb +59 -0
- metadata +70 -60
- data/TODO +0 -308
- data/lib/activefacts/cql/compiler/join.rb +0 -162
- data/lib/activefacts/generate/oo.rb +0 -176
- data/lib/activefacts/generate/ordered.rb +0 -602
@@ -4,8 +4,10 @@
|
|
4
4
|
#
|
5
5
|
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
|
+
require 'activefacts'
|
7
8
|
require 'activefacts/vocabulary'
|
8
|
-
require 'activefacts/generate/oo'
|
9
|
+
require 'activefacts/generate/helpers/oo'
|
10
|
+
require 'activefacts/mapping/rails'
|
9
11
|
|
10
12
|
module ActiveFacts
|
11
13
|
module Generate
|
@@ -15,25 +17,29 @@ module ActiveFacts
|
|
15
17
|
# Options are comma or space separated:
|
16
18
|
# * help list available options
|
17
19
|
# * sql Emit the sql mapping for tables/columns (REVISIT: not functional at present)
|
18
|
-
class RUBY < OO
|
20
|
+
class RUBY < Helpers::OO
|
19
21
|
private
|
20
22
|
|
21
23
|
def set_option(option)
|
22
|
-
@
|
24
|
+
@mapping = false
|
23
25
|
case option
|
24
26
|
when 'help', '?'
|
25
27
|
$stderr.puts "Usage:\t\tafgen --ruby[=option,option] input_file.cql\n"+
|
26
|
-
"
|
28
|
+
"\t\tmapping={sql|rails}\tEmit data to enable mappings to SQL or to Rails"
|
27
29
|
exit 0
|
28
|
-
when
|
30
|
+
when /mapping=(.*)/
|
31
|
+
@mapping = $1
|
32
|
+
@vocabulary.tables
|
29
33
|
else super
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
37
|
def vocabulary_start(vocabulary)
|
34
38
|
puts "require 'activefacts/api'\n"
|
35
|
-
if @
|
39
|
+
if @mapping
|
36
40
|
require 'activefacts/persistence'
|
41
|
+
end
|
42
|
+
if @mapping == 'sql'
|
37
43
|
puts "require 'activefacts/persistence'\n"
|
38
44
|
@tables = vocabulary.tables
|
39
45
|
end
|
@@ -44,6 +50,15 @@ module ActiveFacts
|
|
44
50
|
puts "end"
|
45
51
|
end
|
46
52
|
|
53
|
+
def emit_mapping o
|
54
|
+
case @mapping
|
55
|
+
when 'sql'
|
56
|
+
puts " table"
|
57
|
+
when 'rails'
|
58
|
+
puts " table :#{o.rails_name}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
47
62
|
def value_type_dump(o)
|
48
63
|
length = (l = o.length) && l > 0 ? ":length => #{l}" : nil
|
49
64
|
scale = (s = o.scale) && s > 0 ? ":scale => #{s}" : nil
|
@@ -69,9 +84,7 @@ module ActiveFacts
|
|
69
84
|
|
70
85
|
puts " class #{name} < #{ruby_type_name}\n" +
|
71
86
|
" value_type #{params}\n"
|
72
|
-
|
73
|
-
puts " table"
|
74
|
-
end
|
87
|
+
emit_mapping o if o.is_table
|
75
88
|
puts " restrict #{o.value_constraint.all_allowed_range_sorted.map{|ar| ar.to_s}*", "}\n" if o.value_constraint
|
76
89
|
puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
|
77
90
|
roles_dump(o)
|
@@ -85,9 +98,7 @@ module ActiveFacts
|
|
85
98
|
puts " class #{o.name.gsub(/ /,'')} < #{ primary_supertype.name.gsub(/ /,'') }"
|
86
99
|
puts " identified_by #{identified_by(o, pi)}" if pi
|
87
100
|
puts " supertypes "+secondary_supertypes.map{|st| st.name.gsub(/ /,'')}*", " if secondary_supertypes.size > 0
|
88
|
-
if
|
89
|
-
puts " table"
|
90
|
-
end
|
101
|
+
emit_mapping(o) if o.is_table
|
91
102
|
fact_roles_dump(o.fact_type) if o.fact_type
|
92
103
|
roles_dump(o)
|
93
104
|
puts " end\n\n"
|
@@ -99,9 +110,7 @@ module ActiveFacts
|
|
99
110
|
|
100
111
|
# We want to name the absorption role only when it's absorbed along its single identifying role.
|
101
112
|
puts " identified_by #{identified_by(o, pi)}"
|
102
|
-
|
103
|
-
puts " table"
|
104
|
-
end
|
113
|
+
emit_mapping o if o.is_table
|
105
114
|
fact_roles_dump(o.fact_type) if o.fact_type
|
106
115
|
roles_dump(o)
|
107
116
|
puts " end\n\n"
|
@@ -125,7 +134,7 @@ module ActiveFacts
|
|
125
134
|
"\n" +
|
126
135
|
secondary_supertypes.map{|sst| " supertype :#{sst.name.gsub(/ /,'_')}"}*"\n" +
|
127
136
|
(pi ? " identified_by #{identified_by(o, pi)}" : "")
|
128
|
-
|
137
|
+
emit_mapping o if o.is_table
|
129
138
|
fact_roles_dump(fact_type)
|
130
139
|
roles_dump(o)
|
131
140
|
puts " end\n\n"
|
@@ -144,25 +153,35 @@ module ActiveFacts
|
|
144
153
|
end
|
145
154
|
|
146
155
|
def binary_dump(role, role_name, role_player, mandatory = nil, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
|
156
|
+
ruby_role_name = ":"+role_name.gsub(/ /,'_')
|
157
|
+
|
147
158
|
# Find whether we need the name of the other role player, and whether it's defined yet:
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
else
|
159
|
+
implied_role_name = role_player.name.gsub(/ /,'').sub(/^[a-z]/) {|i| i.upcase}
|
160
|
+
if role_name.camelcase != implied_role_name
|
161
|
+
# Only use Class name if it's not implied by the rolename
|
152
162
|
role_reference = ":class => "+object_type_reference(role_player)
|
153
163
|
end
|
164
|
+
|
154
165
|
other_role_name = ":counterpart => :"+other_role_name.gsub(/ /,'_') if other_role_name
|
155
166
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
167
|
+
if vr = role.role_value_constraint
|
168
|
+
value_restriction = ":restrict => #{vr}"
|
169
|
+
end
|
170
|
+
|
171
|
+
options = [
|
172
|
+
ruby_role_name,
|
173
|
+
role_reference,
|
174
|
+
mandatory ? ":mandatory => true" : nil,
|
175
|
+
readings,
|
176
|
+
other_role_name,
|
177
|
+
value_restriction
|
178
|
+
].compact
|
179
|
+
|
180
|
+
line = " #{one_to_one ? "one_to_one" : "has_one" } #{options*', '} "
|
181
|
+
if other_method_name
|
182
|
+
line += " "*(48-line.length) if line.length < 48
|
183
|
+
line += "\# See #{role_player.name.gsub(/ /,'')}.#{other_method_name}"
|
184
|
+
end
|
166
185
|
puts line
|
167
186
|
#puts " \# REVISIT: #{other_role_name} has values restricted to #{role.role_value_constraint}\n" if role.role_value_constraint
|
168
187
|
end
|
@@ -178,3 +197,5 @@ module ActiveFacts
|
|
178
197
|
end
|
179
198
|
end
|
180
199
|
end
|
200
|
+
|
201
|
+
ActiveFacts::Registry.generator('ruby', ActiveFacts::Generate::RUBY)
|
@@ -9,7 +9,7 @@ require 'activefacts/persistence'
|
|
9
9
|
|
10
10
|
module ActiveFacts
|
11
11
|
module Generate
|
12
|
-
|
12
|
+
module SQL #:nodoc:
|
13
13
|
# Generate SQL for MySQL for an ActiveFacts vocabulary.
|
14
14
|
# Invoke as
|
15
15
|
# afgen --sql/mysql[=options] <file>.cql
|
@@ -165,7 +165,7 @@ module ActiveFacts
|
|
165
165
|
puts "CREATE TABLE #{escape table.name.gsub(' ','')} ("
|
166
166
|
|
167
167
|
pk = table.identifier_columns
|
168
|
-
identity_column = pk[0] if pk
|
168
|
+
identity_column = pk[0] if pk[0].is_auto_assigned
|
169
169
|
|
170
170
|
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
171
171
|
fk_columns = table.columns.select do |column|
|
@@ -245,7 +245,7 @@ module ActiveFacts
|
|
245
245
|
|
246
246
|
private
|
247
247
|
def sql_value(value)
|
248
|
-
value.
|
248
|
+
value.is_literal_string ? sql_string(value.literal) : value.literal
|
249
249
|
end
|
250
250
|
|
251
251
|
def sql_string(str)
|
@@ -276,3 +276,5 @@ module ActiveFacts
|
|
276
276
|
end
|
277
277
|
end
|
278
278
|
end
|
279
|
+
|
280
|
+
ActiveFacts::Registry.generator('sql/mysql', ActiveFacts::Generate::SQL::MYSQL)
|
@@ -9,7 +9,7 @@ require 'activefacts/persistence'
|
|
9
9
|
|
10
10
|
module ActiveFacts
|
11
11
|
module Generate
|
12
|
-
|
12
|
+
module SQL #:nodoc:
|
13
13
|
# Generate SQL for SQL Server for an ActiveFacts vocabulary.
|
14
14
|
# Invoke as
|
15
15
|
# afgen --sql/server[=options] <file>.cql
|
@@ -84,7 +84,7 @@ module ActiveFacts
|
|
84
84
|
when length <= 8
|
85
85
|
'tinyint'
|
86
86
|
when length <= 16
|
87
|
-
'
|
87
|
+
'smallint'
|
88
88
|
when length <= 32
|
89
89
|
'int'
|
90
90
|
else
|
@@ -112,6 +112,8 @@ module ActiveFacts
|
|
112
112
|
when /^Auto ?Time ?Stamp$/
|
113
113
|
'timestamp'
|
114
114
|
|
115
|
+
when /^Guid$/
|
116
|
+
'uniqueidentifier'
|
115
117
|
when /^Money$/
|
116
118
|
'decimal'
|
117
119
|
when /^Picture ?Raw ?Data$/, /^Image$/
|
@@ -137,7 +139,7 @@ module ActiveFacts
|
|
137
139
|
puts "CREATE TABLE #{escape table.name.gsub(' ',@underscore)} ("
|
138
140
|
|
139
141
|
pk = table.identifier_columns
|
140
|
-
identity_column = pk[0] if pk
|
142
|
+
identity_column = pk[0] if pk[0].is_auto_assigned
|
141
143
|
|
142
144
|
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
143
145
|
fk_columns = table.columns.select do |column|
|
@@ -235,7 +237,7 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
|
|
235
237
|
|
236
238
|
private
|
237
239
|
def sql_value(value)
|
238
|
-
value.
|
240
|
+
value.is_literal_string ? sql_string(value.literal) : value.literal
|
239
241
|
end
|
240
242
|
|
241
243
|
def sql_string(str)
|
@@ -266,3 +268,5 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
|
|
266
268
|
end
|
267
269
|
end
|
268
270
|
end
|
271
|
+
|
272
|
+
ActiveFacts::Registry.generator('sql/server', ActiveFacts::Generate::SQL::SERVER)
|
@@ -0,0 +1,209 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Schema Transform
|
3
|
+
# Transform a loaded ActiveFacts vocabulary to suit ActiveRecord
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
require 'activefacts/vocabulary'
|
8
|
+
require 'activefacts/persistence'
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Metamodel
|
12
|
+
class ObjectType
|
13
|
+
|
14
|
+
def add_surrogate type_name = 'Auto Counter', suffix = 'ID'
|
15
|
+
# Find or assert the surrogate value type
|
16
|
+
auto_counter = vocabulary.valid_value_type_name(type_name) ||
|
17
|
+
constellation.ValueType(:vocabulary => vocabulary, :name => type_name, :guid => :new)
|
18
|
+
|
19
|
+
# Create a subtype to identify this entity type:
|
20
|
+
vt_name = self.name + ' '+suffix
|
21
|
+
my_id = @vocabulary.valid_value_type_name(vt_name) ||
|
22
|
+
constellation.ValueType(:vocabulary => vocabulary, :name => vt_name, :guid => :new, :supertype => auto_counter)
|
23
|
+
|
24
|
+
# Create a fact type
|
25
|
+
identifying_fact_type = constellation.FactType(:guid => :new)
|
26
|
+
my_role = constellation.Role(:guid => :new, :fact_type => identifying_fact_type, :ordinal => 0, :object_type => self)
|
27
|
+
@injected_surrogate_role = my_role
|
28
|
+
id_role = constellation.Role(:guid => :new, :fact_type => identifying_fact_type, :ordinal => 1, :object_type => my_id)
|
29
|
+
|
30
|
+
# Create a reading (which needs a RoleSequence)
|
31
|
+
reading = constellation.Reading(
|
32
|
+
:fact_type => identifying_fact_type,
|
33
|
+
:ordinal => 0,
|
34
|
+
:role_sequence => [:new],
|
35
|
+
:text => "{0} has {1}"
|
36
|
+
)
|
37
|
+
constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 0, :role => my_role)
|
38
|
+
constellation.RoleRef(:role_sequence => reading.role_sequence, :ordinal => 1, :role => id_role)
|
39
|
+
|
40
|
+
# Create two uniqueness constraints for the one-to-one. Each needs a RoleSequence (two RoleRefs)
|
41
|
+
one_id = constellation.PresenceConstraint(
|
42
|
+
:guid => :new,
|
43
|
+
:vocabulary => vocabulary,
|
44
|
+
:name => self.name+'HasOne'+suffix,
|
45
|
+
:role_sequence => [:new],
|
46
|
+
:is_mandatory => true,
|
47
|
+
:min_frequency => 1,
|
48
|
+
:max_frequency => 1,
|
49
|
+
:is_preferred_identifier => false
|
50
|
+
)
|
51
|
+
@constellation.RoleRef(:role_sequence => one_id.role_sequence, :ordinal => 0, :role => my_role)
|
52
|
+
|
53
|
+
one_me = constellation.PresenceConstraint(
|
54
|
+
:guid => :new,
|
55
|
+
:vocabulary => vocabulary,
|
56
|
+
:name => self.name+suffix+'IsOfOne'+self.name,
|
57
|
+
:role_sequence => [:new],
|
58
|
+
:is_mandatory => false,
|
59
|
+
:min_frequency => 0,
|
60
|
+
:max_frequency => 1,
|
61
|
+
:is_preferred_identifier => true
|
62
|
+
)
|
63
|
+
@constellation.RoleRef(:role_sequence => one_me.role_sequence, :ordinal => 0, :role => id_role)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ValueType
|
68
|
+
def needs_surrogate
|
69
|
+
supertype_names = supertypes_transitive.map(&:name)
|
70
|
+
!(supertype_names.include?('Auto Counter') or supertype_names.include?('Guid') or supertype_names.include?('ID'))
|
71
|
+
end
|
72
|
+
|
73
|
+
def inject_surrogate
|
74
|
+
debug :transform_surrogate, "Adding surrogate ID to Value Type #{name}"
|
75
|
+
add_surrogate('Auto Counter', 'ID')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class EntityType
|
80
|
+
def identifying_refs_from
|
81
|
+
pi = preferred_identifier
|
82
|
+
rrs = pi.role_sequence.all_role_ref
|
83
|
+
|
84
|
+
# REVISIT: This is actually a ref to us, not from
|
85
|
+
# if absorbed_via
|
86
|
+
# return [absorbed_via]
|
87
|
+
# end
|
88
|
+
|
89
|
+
rrs.map do |rr|
|
90
|
+
r = references_from.detect{|ref| rr.role == ref.to_role }
|
91
|
+
raise "failed to find #{name} identifying reference for #{rr.role.object_type.name} in #{references_from.inspect}" unless r
|
92
|
+
r
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def needs_surrogate
|
97
|
+
|
98
|
+
# A recursive proc to replace any reference to an Entity Type by its identifying references:
|
99
|
+
debug :transform_surrogate_expansion, "Expanding key for #{name}"
|
100
|
+
substitute_identifying_refs = proc do |object|
|
101
|
+
if ref = object.absorbed_via
|
102
|
+
# This shouldn't be necessary, but see the absorbed_via comment above.
|
103
|
+
absorbed_into = ref.from
|
104
|
+
debug :transform_surrogate_expansion, "recursing to handle absorption of #{object.name} into #{absorbed_into.name}"
|
105
|
+
[substitute_identifying_refs.call(absorbed_into)]
|
106
|
+
else
|
107
|
+
irf = object.identifying_refs_from
|
108
|
+
debug :transform_surrogate_expansion, "Iterating for #{object.name} over #{irf.inspect}" do
|
109
|
+
irf.each_with_index do |ref, i|
|
110
|
+
next if ref.is_unary
|
111
|
+
next if ref.to_role.object_type.kind_of?(ActiveFacts::Metamodel::ValueType)
|
112
|
+
recurse_to = ref.to_role.object_type
|
113
|
+
|
114
|
+
debug :transform_surrogate_expansion, "#{i}: recursing to expand #{recurse_to.name} key in #{ref}" do
|
115
|
+
irf[i] = substitute_identifying_refs.call(recurse_to)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
irf
|
120
|
+
end
|
121
|
+
end
|
122
|
+
irf = substitute_identifying_refs.call(self)
|
123
|
+
|
124
|
+
debug :transform_surrogate, "Does #{name} need a surrogate? it's identified by #{irf.inspect}" do
|
125
|
+
|
126
|
+
pk_fks = identifying_refs_from.map do |ref|
|
127
|
+
ref.to && ref.to.is_table ? ref.to : nil
|
128
|
+
end
|
129
|
+
|
130
|
+
irf.flatten!
|
131
|
+
|
132
|
+
# Multi-part identifiers are only allowed if each part is a foreign key (i.e. it's a join table) and the object is not the target of a foreign key:
|
133
|
+
if irf.size >= 2
|
134
|
+
if pk_fks.include?(nil)
|
135
|
+
debug :transform_surrogate, "#{self.name} needs a surrogate because its multi-part key contains a non-table"
|
136
|
+
return true
|
137
|
+
elsif references_to.size != 0
|
138
|
+
debug :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect} but is also an FK target"
|
139
|
+
return true
|
140
|
+
else
|
141
|
+
debug :transform_surrogate, "#{self.name} is a join table between #{pk_fks.map(&:name).inspect}"
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
return true
|
145
|
+
end
|
146
|
+
|
147
|
+
# Single-part key. It must be an Auto Counter, or we will add a surrogate
|
148
|
+
|
149
|
+
identifying_type = irf[0].to
|
150
|
+
if identifying_type.needs_surrogate
|
151
|
+
debug :transform_surrogate, "#{self.name} needs a surrogate because #{irf[0].to.name} is not an AutoCounter, but #{identifying_type.supertypes_transitive.map(&:name).inspect}"
|
152
|
+
return true
|
153
|
+
end
|
154
|
+
|
155
|
+
false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def inject_surrogate
|
160
|
+
debug :transform_surrogate, "Injecting a surrogate key into #{self.name}"
|
161
|
+
|
162
|
+
# Disable the preferred identifier:
|
163
|
+
pi = preferred_identifier
|
164
|
+
debug :transform_surrogate, "pi for #{name} was '#{pi.describe}'"
|
165
|
+
pi.is_preferred_identifier = false
|
166
|
+
@preferred_identifier = nil # Kill the cache
|
167
|
+
|
168
|
+
add_surrogate
|
169
|
+
|
170
|
+
debug :transform_surrogate, "pi for #{name} is now '#{preferred_identifier.describe}'"
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
module Persistence
|
177
|
+
class Column
|
178
|
+
def is_injected_surrogate
|
179
|
+
references.size == 1 and
|
180
|
+
references[0].from_role == references[0].from.injected_surrogate_role
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
module Generate #:nodoc:
|
186
|
+
module Transform #:nodoc:
|
187
|
+
class Surrogate
|
188
|
+
def initialize(vocabulary, *options)
|
189
|
+
@vocabulary = vocabulary
|
190
|
+
end
|
191
|
+
|
192
|
+
def generate(out = $stdout)
|
193
|
+
@out = out
|
194
|
+
injections =
|
195
|
+
@vocabulary.tables.select do |table|
|
196
|
+
table.needs_surrogate
|
197
|
+
end
|
198
|
+
injections.each do |table|
|
199
|
+
table.inject_surrogate
|
200
|
+
end
|
201
|
+
|
202
|
+
@vocabulary.decide_tables
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
ActiveFacts::Registry.generator('transform/surrogate', ActiveFacts::Generate::Transform::Surrogate)
|