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.
- 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
@@ -0,0 +1,307 @@
|
|
1
|
+
#
|
2
|
+
# Calculate the relational composition of a given Vocabulary
|
3
|
+
# The composition consists of decisiona about which Concepts are tables,
|
4
|
+
# and what columns (absorbed roled) those tables will have.
|
5
|
+
#
|
6
|
+
# This module has the following known problems:
|
7
|
+
#
|
8
|
+
# * Some one-to-ones absorb in both directions (ET<->FT in Metamodel, Blog model)
|
9
|
+
#
|
10
|
+
# * When a subtype has no mandatory roles, we should introduce
|
11
|
+
# a binary (is_subtype) to indicate it's that subtype.
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'activefacts/persistence/reference'
|
15
|
+
|
16
|
+
module ActiveFacts
|
17
|
+
module Metamodel
|
18
|
+
|
19
|
+
class ValueType
|
20
|
+
def absorbed_via; nil; end # ValueTypes aren't absorbed in the way EntityTypes are
|
21
|
+
|
22
|
+
# Say whether this object is currently considered a table or not:
|
23
|
+
def is_table
|
24
|
+
return @is_table if @is_table != nil
|
25
|
+
|
26
|
+
# Always a table if marked so:
|
27
|
+
if is_independent
|
28
|
+
debug :absorption, "ValueType #{name} is declared independent"
|
29
|
+
@tentative = false
|
30
|
+
return @is_table = true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Only a table if it has references (to another ValueType)
|
34
|
+
if !references_from.empty?
|
35
|
+
debug :absorption, "#{name} is a table because it has #{references_from.size} references to it"
|
36
|
+
@is_table = true
|
37
|
+
else
|
38
|
+
@is_table = false
|
39
|
+
end
|
40
|
+
@tentative = false
|
41
|
+
|
42
|
+
@is_table
|
43
|
+
end
|
44
|
+
|
45
|
+
# REVISIT: Find a better way to determine AutoCounters (ValueType unary role?)
|
46
|
+
def is_auto_assigned
|
47
|
+
type = self;
|
48
|
+
type = type.supertype while type.supertype
|
49
|
+
type.name =~ /^Auto/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class EntityType
|
54
|
+
attr_accessor :absorbed_via # A reference from an entity type that fully absorbs this one
|
55
|
+
|
56
|
+
def is_auto_assigned; false; end
|
57
|
+
|
58
|
+
# Decide whether this object is currently considered a table or not:
|
59
|
+
def is_table
|
60
|
+
return @is_table if @is_table != nil # We already make a guess or decision
|
61
|
+
|
62
|
+
@tentative = false
|
63
|
+
|
64
|
+
# Always a table if marked so
|
65
|
+
if is_independent
|
66
|
+
debug :absorption, "EntityType #{name} is declared independent"
|
67
|
+
return @is_table = true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Always a table if nowhere else to go, and has no one-to-ones that might flip:
|
71
|
+
if references_to.empty? and
|
72
|
+
!references_from.detect{|ref| ref.role_type == :one_one }
|
73
|
+
debug :absorption, "EntityType #{name} is independent as it has nowhere to go"
|
74
|
+
return @is_table = true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Subtypes are not a table unless partitioned or separate
|
78
|
+
# REVISIT: Support partitioned subtypes here
|
79
|
+
if (!supertypes.empty?)
|
80
|
+
av = all_supertype_inheritance[0]
|
81
|
+
return @is_table = false
|
82
|
+
end
|
83
|
+
|
84
|
+
# If the preferred_identifier includes an auto_assigned ValueType
|
85
|
+
# and this object is absorbed in more than one place, we need a table
|
86
|
+
# to manage the auto-assignment.
|
87
|
+
if references_to.size > 1 and
|
88
|
+
preferred_identifier.role_sequence.all_role_ref.detect {|rr|
|
89
|
+
next false unless rr.role.concept.is_a? ValueType
|
90
|
+
rr.role.concept.is_auto_assigned
|
91
|
+
}
|
92
|
+
debug :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
|
93
|
+
@tentative = false
|
94
|
+
return @is_table = true
|
95
|
+
end
|
96
|
+
|
97
|
+
@tentative = true
|
98
|
+
@is_table = true
|
99
|
+
end
|
100
|
+
end # EntityType class
|
101
|
+
|
102
|
+
class Role
|
103
|
+
def role_type
|
104
|
+
# TypeInheritance roles are always 1:1
|
105
|
+
if TypeInheritance === fact_type
|
106
|
+
return concept == fact_type.supertype ? :supertype : :subtype
|
107
|
+
end
|
108
|
+
|
109
|
+
# Always N:1 if unary:
|
110
|
+
return :unary if fact_type.all_role.size == 1
|
111
|
+
|
112
|
+
# List the UCs on this fact type:
|
113
|
+
all_uniqueness_constraints =
|
114
|
+
fact_type.all_role.map do |fact_role|
|
115
|
+
fact_role.all_role_ref.map do |rr|
|
116
|
+
rr.role_sequence.all_presence_constraint.select do |pc|
|
117
|
+
pc.max_frequency == 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end.flatten.uniq
|
121
|
+
|
122
|
+
to_1 =
|
123
|
+
all_uniqueness_constraints.
|
124
|
+
detect do |c|
|
125
|
+
c.role_sequence.all_role_ref.size == 1 and
|
126
|
+
c.role_sequence.all_role_ref[0].role == self
|
127
|
+
end
|
128
|
+
|
129
|
+
if fact_type.entity_type
|
130
|
+
# This is a role in an objectified fact type
|
131
|
+
from_1 = true
|
132
|
+
else
|
133
|
+
# It's to-1 if a UC exists over roles of this FT that doesn't cover this role:
|
134
|
+
from_1 = all_uniqueness_constraints.detect{|uc|
|
135
|
+
!uc.role_sequence.all_role_ref.detect{|rr| rr.role == self || rr.role.fact_type != fact_type}
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
if from_1
|
140
|
+
return to_1 ? :one_one : :one_many
|
141
|
+
else
|
142
|
+
return to_1 ? :many_one : :many_many
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class Vocabulary
|
149
|
+
# return an Array of Concepts that will have their own tables
|
150
|
+
def tables
|
151
|
+
decide_tables if !@tables
|
152
|
+
@tables
|
153
|
+
end
|
154
|
+
|
155
|
+
def decide_tables
|
156
|
+
# Strategy:
|
157
|
+
# 1) Populate references for all Concepts
|
158
|
+
# 2) Decide which Concepts must be and must not be tables
|
159
|
+
# a. Concepts labelled is_independent are tables (See the is_table methods above)
|
160
|
+
# b. Entity types having no references to them must be tables
|
161
|
+
# c. subtypes are not tables unless marked is_independent (separate) or partitioned (not yet impl)
|
162
|
+
# d. ValueTypes are never tables unless they can have references (to other ValueTypes)
|
163
|
+
# e. An EntityType having an identifying AutoInc field must be a table unless it has exactly one reference
|
164
|
+
# f. An EntityType whose only reference is through its single preferred_identifier role gets absorbed
|
165
|
+
# g. An EntityType that must has references other than its PI must be a table (unless it has exactly one reference to it)
|
166
|
+
# h. supertypes are elided if all roles are absorbed into subtypes:
|
167
|
+
# - partitioned subtype exhaustion
|
168
|
+
# - subtype extension where supertype has only PI roles and no AutoInc
|
169
|
+
# 3) any ValueType that has references from it must become a table if not already
|
170
|
+
|
171
|
+
populate_all_references
|
172
|
+
|
173
|
+
debug :absorption, "Calculating relational composition" do
|
174
|
+
# Evaluate the possible independence of each concept, building an array of features of indeterminate status:
|
175
|
+
undecided =
|
176
|
+
all_feature.select do |feature|
|
177
|
+
next unless feature.is_a? Concept
|
178
|
+
feature.is_table # Ask it whether it thinks it should be a table
|
179
|
+
feature.tentative # Selection criterion
|
180
|
+
end
|
181
|
+
|
182
|
+
if debug :absorption, "Generating tables, #{undecided.size} undecided"
|
183
|
+
(all_feature-undecided).each {|feature|
|
184
|
+
next if ValueType === feature && !feature.is_table # Skip unremarkable cases
|
185
|
+
debug :absorption do
|
186
|
+
debug :absorption, "#{feature.name} is #{feature.is_table ? "" : "not "}a table#{feature.tentative ? ", tentatively" : ""}"
|
187
|
+
end
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
pass = 0
|
192
|
+
begin # Loop while we continue to make progress
|
193
|
+
pass += 1
|
194
|
+
debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
|
195
|
+
possible_flips = {} # A hash by table containing an array of references that can be flipped
|
196
|
+
finalised = # Make an array of things we finalised during this pass
|
197
|
+
undecided.select do |feature|
|
198
|
+
debug :absorption, "Considering #{feature.name}:" do
|
199
|
+
debug :absorption, "refs to #{feature.name} are from #{feature.references_to.map{|ref| ref.from.name}*", "}" if feature.references_to.size > 0
|
200
|
+
debug :absorption, "refs from #{feature.name} are to #{feature.references_from.map{|ref| ref.to.name rescue ref.fact_type.default_reading}*", "}" if feature.references_from.size > 0
|
201
|
+
|
202
|
+
# Always absorb an objectified unary into its role player:
|
203
|
+
if feature.fact_type && feature.fact_type.all_role.size == 1
|
204
|
+
debug :absorption, "Absorb objectified unary #{feature.name} into #{feature.fact_type.entity_type.name}"
|
205
|
+
feature.definitely_not_table
|
206
|
+
next feature
|
207
|
+
end
|
208
|
+
|
209
|
+
# If the PI contains one role only, played by an entity type that can absorb us, do that.
|
210
|
+
pi_roles = feature.preferred_identifier.role_sequence.all_role_ref.map(&:role)
|
211
|
+
debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.concept.name}*", "}"
|
212
|
+
first_pi_role = pi_roles[0]
|
213
|
+
pi_ref = nil
|
214
|
+
if pi_roles.size == 1 and
|
215
|
+
feature.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
|
216
|
+
|
217
|
+
debug :absorption, "#{feature.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
|
218
|
+
feature.definitely_not_table
|
219
|
+
next feature
|
220
|
+
end
|
221
|
+
|
222
|
+
# If there's more than one absorption path and any functional dependencies that can't absorb us, it's a table
|
223
|
+
non_identifying_refs_from =
|
224
|
+
feature.references_from.reject{|ref|
|
225
|
+
pi_roles.include?(ref.to_role)
|
226
|
+
}
|
227
|
+
debug :absorption, "#{feature.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
|
228
|
+
|
229
|
+
if feature.references_to.size > 1 and
|
230
|
+
non_identifying_refs_from.size > 0
|
231
|
+
debug :absorption, "#{feature.name} has non-identifying functional dependencies so 3NF requires it be a table"
|
232
|
+
feature.definitely_table
|
233
|
+
next feature
|
234
|
+
end
|
235
|
+
|
236
|
+
absorption_paths =
|
237
|
+
(
|
238
|
+
non_identifying_refs_from.reject do |ref|
|
239
|
+
!ref.to or ref.to.absorbed_via == ref
|
240
|
+
end+feature.references_to
|
241
|
+
).reject do |ref|
|
242
|
+
next true if !ref.to.is_table or
|
243
|
+
![:one_one, :supertype, :subtype].include?(ref.role_type)
|
244
|
+
|
245
|
+
# If one side is mandatory but not the other, don't absorb the mandatory side into the non-mandatory one
|
246
|
+
from_is_mandatory = !!ref.is_mandatory
|
247
|
+
to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
|
248
|
+
bad = (to_is_mandatory != from_is_mandatory and (ref.from == feature ? from_is_mandatory : to_is_mandatory))
|
249
|
+
debug :absorption, "Not absorbing mandatory #{feature.name} through #{ref}" if bad
|
250
|
+
bad
|
251
|
+
end
|
252
|
+
|
253
|
+
# If this object can be fully absorbed, do that (might require flipping some references)
|
254
|
+
if absorption_paths.size > 0
|
255
|
+
debug :absorption, "#{feature.name} is fully absorbed through #{absorption_paths.inspect}"
|
256
|
+
absorption_paths.each do |ref|
|
257
|
+
debug :absorption, "flip #{ref} so #{feature.name} can be absorbed"
|
258
|
+
ref.flip if feature == ref.from
|
259
|
+
end
|
260
|
+
feature.definitely_not_table
|
261
|
+
next feature
|
262
|
+
end
|
263
|
+
|
264
|
+
if non_identifying_refs_from.size == 0
|
265
|
+
# and (!feature.is_a?(EntityType) ||
|
266
|
+
# # REVISIT: The roles may be collectively but not individually mandatory.
|
267
|
+
# feature.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
|
268
|
+
debug :absorption, "#{feature.name} is fully absorbed in #{feature.references_to.size} places: #{feature.references_to.map{|ref| ref.from.name}*", "}"
|
269
|
+
feature.definitely_not_table
|
270
|
+
next feature
|
271
|
+
end
|
272
|
+
|
273
|
+
false # Failed to decide about this entity_type this time around
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
undecided -= finalised
|
278
|
+
debug :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
|
279
|
+
end while !finalised.empty?
|
280
|
+
|
281
|
+
# A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
|
282
|
+
# unless it should absorb something else (another ValueType is all it could be):
|
283
|
+
all_feature.each do |feature|
|
284
|
+
if (!feature.is_table and feature.references_to.size == 0 and feature.references_from.size > 0)
|
285
|
+
debug :absorption, "Making #{feature.name} a table; it has nowhere else to go and needs to absorb things"
|
286
|
+
feature.probably_table
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# Now, evaluate all possibilities of the tentative assignments
|
291
|
+
# Incomplete. Apparently unnecessary as well... so far. We'll see.
|
292
|
+
if debug :absorption
|
293
|
+
undecided.each do |feature|
|
294
|
+
debug :absorption, "Unable to decide independence of #{feature.name}, going with #{feature.show_tabular}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
populate_all_columns
|
300
|
+
populate_all_indices
|
301
|
+
|
302
|
+
@tables = all_feature.select { |f| f.is_table }
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
end
|
data/lib/activefacts/support.rb
CHANGED
data/lib/activefacts/version.rb
CHANGED
@@ -40,6 +40,16 @@ module ActiveFacts
|
|
40
40
|
}
|
41
41
|
end
|
42
42
|
|
43
|
+
def is_mandatory
|
44
|
+
all_role_ref.detect{|rr|
|
45
|
+
rs = rr.role_sequence
|
46
|
+
rs.all_role_ref.size == 1 and
|
47
|
+
rs.all_presence_constraint.detect{|pc|
|
48
|
+
pc.min_frequency and pc.min_frequency >= 1 and pc.is_mandatory
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
43
53
|
# Return the RoleRef to this role from its fact type's preferred_reading
|
44
54
|
def preferred_reference
|
45
55
|
fact_type.preferred_reading.role_sequence.all_role_ref.detect{|rr| rr.role == self }
|
@@ -73,6 +83,19 @@ module ActiveFacts
|
|
73
83
|
end
|
74
84
|
return joiner ? Array(name_array)*joiner : Array(name_array)
|
75
85
|
end
|
86
|
+
|
87
|
+
# Two RoleRefs are equal if they have the same role and JoinPaths with matching roles
|
88
|
+
def ==(role_ref)
|
89
|
+
RoleRef === role_ref &&
|
90
|
+
role_ref.role == role &&
|
91
|
+
all_join_path.size == role_ref.all_join_path.size &&
|
92
|
+
!all_join_path.sort_by{|j|j.join_step}.
|
93
|
+
zip(role_ref.all_join_path.sort_by{|j|j.join_step}).
|
94
|
+
detect{|j1,j2|
|
95
|
+
j1.input_role != j2.input_role ||
|
96
|
+
j1.output_role != j2.output_role
|
97
|
+
}
|
98
|
+
end
|
76
99
|
end
|
77
100
|
|
78
101
|
class RoleSequence
|
@@ -107,9 +130,23 @@ module ActiveFacts
|
|
107
130
|
role_sequence = rr.role_sequence
|
108
131
|
|
109
132
|
# The role sequence is only interesting if it cover only this fact's roles
|
133
|
+
# or roles of the objectification
|
110
134
|
next if role_sequence.all_role_ref.size < fact_roles.size-1 # Not enough roles
|
111
135
|
next if role_sequence.all_role_ref.size > fact_roles.size # Too many roles
|
112
|
-
next if role_sequence.all_role_ref.detect
|
136
|
+
next if role_sequence.all_role_ref.detect do |rsr|
|
137
|
+
if (of = rsr.role.fact_type) != fact_type
|
138
|
+
case of.all_role.size
|
139
|
+
when 1 # A unary FT must be played by the objectification of this fact type
|
140
|
+
next rsr.role.concept != fact_type.entity_type
|
141
|
+
when 2 # A binary FT must have the objectification of this FT as the other player
|
142
|
+
other_role = (of.all_role-[rsr.role])[0]
|
143
|
+
next other_role.concept != fact_type.entity_type
|
144
|
+
else
|
145
|
+
next true # A role in a ternary (or higher) cannot be usd in our identifier
|
146
|
+
end
|
147
|
+
end
|
148
|
+
rsr.role.fact_type != fact_type
|
149
|
+
end
|
113
150
|
|
114
151
|
# This role sequence is a candidate
|
115
152
|
pc = role_sequence.all_presence_constraint.detect{|c|
|
@@ -243,10 +280,10 @@ module ActiveFacts
|
|
243
280
|
|
244
281
|
# An array of self followed by all supertypes in order:
|
245
282
|
def supertypes_transitive
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
283
|
+
([self] + all_type_inheritance_by_subtype.map{|ti|
|
284
|
+
# debug ti.class.roles.verbalise; exit
|
285
|
+
ti.supertype.supertypes_transitive
|
286
|
+
}).flatten.uniq
|
250
287
|
end
|
251
288
|
|
252
289
|
# A subtype does not have a identifying_supertype if it defines its own identifier
|
data/spec/absorption_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe "Absorption" do
|
|
43
43
|
#{Prologue}
|
44
44
|
Month is in exactly one Season;
|
45
45
|
},
|
46
|
-
:tables => { "Month" => [
|
46
|
+
:tables => { "Month" => [ "MonthValue", "Season" ] }
|
47
47
|
},
|
48
48
|
|
49
49
|
{ :should => "absorb a one-to-one along the identification path",
|
@@ -64,7 +64,7 @@ describe "Absorption" do
|
|
64
64
|
},
|
65
65
|
:tables => {
|
66
66
|
"Claim" => ["ClaimID", %w{Lodgement DateTime}, %w{Lodgement Person ID}],
|
67
|
-
"Party" => ["PartyID", %w{Person
|
67
|
+
"Party" => ["PartyID", %w{Person Birth Date}]
|
68
68
|
}
|
69
69
|
},
|
70
70
|
|
@@ -81,13 +81,15 @@ describe "Absorption" do
|
|
81
81
|
@compiler = ActiveFacts::Input::CQL.new(cql, should)
|
82
82
|
@vocabulary = @compiler.read
|
83
83
|
|
84
|
+
# puts cql
|
85
|
+
|
84
86
|
# Ensure that the same tables were generated:
|
85
|
-
tables = @vocabulary.tables
|
86
|
-
tables.map(&:name).
|
87
|
+
tables = @vocabulary.tables.sort_by(&:name)
|
88
|
+
tables.map(&:name).should == expected_tables.keys.sort
|
87
89
|
|
88
90
|
# Ensure that the same column descriptions were generated:
|
89
|
-
tables.
|
90
|
-
column_descriptions = table.
|
91
|
+
tables.each do |table|
|
92
|
+
column_descriptions = table.columns.map{|col| col.name(nil) }.sort
|
91
93
|
column_descriptions.should == expected_tables[table.name].map{|c| Array(c) }.sort
|
92
94
|
end
|
93
95
|
end
|
data/spec/cql_cql_spec.rb
CHANGED
data/spec/cql_sql_spec.rb
CHANGED
@@ -17,6 +17,7 @@ describe "CQL Loader with SQL output" do
|
|
17
17
|
Airline
|
18
18
|
CompanyQuery
|
19
19
|
Insurance
|
20
|
+
Marriage
|
20
21
|
OrienteeringER
|
21
22
|
ServiceDirector
|
22
23
|
SimplestUnary
|
@@ -25,7 +26,7 @@ describe "CQL Loader with SQL output" do
|
|
25
26
|
# Generate and return the SQL for the given vocabulary
|
26
27
|
def sql(vocabulary)
|
27
28
|
output = StringIO.new
|
28
|
-
@dumper = ActiveFacts::Generate::SQL::SERVER.new(vocabulary.constellation)
|
29
|
+
@dumper = ActiveFacts::Generate::SQL::SERVER.new(vocabulary.constellation, "norma")
|
29
30
|
@dumper.generate(output)
|
30
31
|
output.rewind
|
31
32
|
output.read
|