activefacts 0.7.3 → 0.8.5
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/LICENSE +19 -0
- data/Manifest.txt +24 -2
- data/Rakefile +25 -3
- data/bin/afgen +1 -1
- data/bin/cql +13 -2
- data/css/offline.css +3 -0
- data/css/orm2.css +24 -0
- data/css/print.css +8 -0
- data/css/style-print.css +357 -0
- data/css/style.css +387 -0
- data/download.html +85 -0
- data/examples/CQL/Address.cql +3 -3
- data/examples/CQL/Blog.cql +13 -14
- data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
- data/examples/CQL/Death.cql +3 -2
- data/examples/CQL/Genealogy.cql +13 -11
- data/examples/CQL/Marriage.cql +2 -2
- data/examples/CQL/Metamodel.cql +136 -93
- data/examples/CQL/MultiInheritance.cql +2 -2
- data/examples/CQL/OilSupply.cql +14 -10
- data/examples/CQL/Orienteering.cql +22 -19
- data/examples/CQL/PersonPlaysGame.cql +3 -2
- data/examples/CQL/SchoolActivities.cql +4 -2
- data/examples/CQL/SimplestUnary.cql +1 -1
- data/examples/CQL/SubtypePI.cql +6 -7
- data/examples/CQL/Warehousing.cql +16 -19
- data/examples/CQL/unit.cql +584 -0
- data/examples/index.html +276 -0
- data/examples/intro.html +497 -0
- data/examples/local.css +20 -0
- data/index.html +96 -0
- data/lib/activefacts/api/concept.rb +48 -46
- data/lib/activefacts/api/constellation.rb +43 -23
- data/lib/activefacts/api/entity.rb +2 -2
- data/lib/activefacts/api/instance.rb +6 -2
- data/lib/activefacts/api/instance_index.rb +5 -0
- data/lib/activefacts/api/value.rb +8 -2
- data/lib/activefacts/api/vocabulary.rb +15 -10
- data/lib/activefacts/cql/CQLParser.treetop +109 -88
- data/lib/activefacts/cql/Concepts.treetop +32 -10
- data/lib/activefacts/cql/Context.treetop +34 -0
- data/lib/activefacts/cql/Expressions.treetop +9 -9
- data/lib/activefacts/cql/FactTypes.treetop +30 -31
- data/lib/activefacts/cql/Language/English.treetop +50 -0
- data/lib/activefacts/cql/LexicalRules.treetop +2 -1
- data/lib/activefacts/cql/Terms.treetop +117 -0
- data/lib/activefacts/cql/ValueTypes.treetop +152 -0
- data/lib/activefacts/cql/compiler.rb +1718 -0
- data/lib/activefacts/cql/parser.rb +124 -57
- data/lib/activefacts/generate/absorption.rb +1 -1
- data/lib/activefacts/generate/cql.rb +111 -100
- data/lib/activefacts/generate/cql/html.rb +5 -5
- data/lib/activefacts/generate/oo.rb +3 -3
- data/lib/activefacts/generate/ordered.rb +51 -19
- data/lib/activefacts/generate/ruby.rb +10 -8
- data/lib/activefacts/generate/sql/mysql.rb +14 -10
- data/lib/activefacts/generate/sql/server.rb +29 -24
- data/lib/activefacts/input/cql.rb +9 -1264
- data/lib/activefacts/input/orm.rb +213 -200
- data/lib/activefacts/persistence/columns.rb +11 -10
- data/lib/activefacts/persistence/index.rb +15 -18
- data/lib/activefacts/persistence/reference.rb +17 -17
- data/lib/activefacts/persistence/tables.rb +50 -51
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +79 -8
- data/lib/activefacts/vocabulary/metamodel.rb +183 -114
- data/spec/absorption_ruby_spec.rb +99 -0
- data/spec/absorption_spec.rb +3 -4
- data/spec/api/constellation.rb +1 -1
- data/spec/api/entity_type.rb +3 -1
- data/spec/api/instance.rb +4 -2
- data/spec/api/roles.rb +8 -6
- data/spec/api_spec.rb +1 -2
- data/spec/cql/context_spec.rb +71 -0
- data/spec/cql/samples_spec.rb +154 -0
- data/spec/cql/unit_spec.rb +375 -0
- data/spec/cql_cql_spec.rb +31 -21
- data/spec/cql_mysql_spec.rb +70 -0
- data/spec/cql_parse_spec.rb +15 -9
- data/spec/cql_ruby_spec.rb +27 -13
- data/spec/cql_sql_spec.rb +42 -16
- data/spec/cql_symbol_tables_spec.rb +2 -3
- data/spec/cqldump_spec.rb +7 -7
- data/spec/helpers/file_matcher.rb +39 -0
- data/spec/norma_cql_spec.rb +20 -12
- data/spec/norma_ruby_spec.rb +6 -3
- data/spec/norma_sql_spec.rb +6 -3
- data/spec/norma_tables_spec.rb +6 -4
- data/spec/spec_helper.rb +27 -8
- data/status.html +69 -0
- data/why.html +60 -0
- metadata +34 -11
- data/lib/activefacts/cql/DataTypes.treetop +0 -81
- data/spec/cql_unit_spec.rb +0 -330
@@ -62,8 +62,8 @@ module ActiveFacts
|
|
62
62
|
def value_type_dump(o)
|
63
63
|
return unless o.supertype # An imported type
|
64
64
|
if o.name == o.supertype.name
|
65
|
-
# In ActiveFacts, parameterising a ValueType will create a new
|
66
|
-
# throw Can't handle parameterized value type of same name as its
|
65
|
+
# In ActiveFacts, parameterising a ValueType will create a new ValueType
|
66
|
+
# throw Can't handle parameterized value type of same name as its ValueType" if ...
|
67
67
|
end
|
68
68
|
|
69
69
|
parameters =
|
@@ -139,13 +139,13 @@ module ActiveFacts
|
|
139
139
|
# Detect standard reference-mode readings:
|
140
140
|
forward_reading = reverse_reading = nil
|
141
141
|
ft.all_reading.each do |reading|
|
142
|
-
if reading.
|
142
|
+
if reading.text =~ /^\{(\d)\} has \{\d\}$/
|
143
143
|
if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
|
144
144
|
forward_reading = reading
|
145
145
|
else
|
146
146
|
reverse_reading = reading
|
147
147
|
end
|
148
|
-
elsif reading.
|
148
|
+
elsif reading.text =~ /^\{(\d)\} is of \{\d\}$/
|
149
149
|
if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
|
150
150
|
reverse_reading = reading
|
151
151
|
else
|
@@ -356,7 +356,7 @@ module ActiveFacts
|
|
356
356
|
}
|
357
357
|
frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] =~ /some/ }
|
358
358
|
|
359
|
-
#$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.
|
359
|
+
#$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
|
360
360
|
|
361
361
|
# REVISIT: Make sure that we refer to the constrained players by their common supertype
|
362
362
|
|
@@ -98,7 +98,7 @@ module ActiveFacts
|
|
98
98
|
role_name = role_method
|
99
99
|
role_name = nil if role_name == role.concept.name.snakecase
|
100
100
|
|
101
|
-
binary_dump(role, other_role_name, other_player, one_to_one, nil, role_name, other_role_method)
|
101
|
+
binary_dump(role, other_role_name, other_player, role.is_mandatory, one_to_one, nil, role_name, other_role_method)
|
102
102
|
end
|
103
103
|
|
104
104
|
def preferred_role_name(role, is_for = nil)
|
@@ -117,7 +117,7 @@ module ActiveFacts
|
|
117
117
|
# Unaries are a hack, with only one role for what is effectively a binary:
|
118
118
|
if (role.fact_type.all_role.size == 1)
|
119
119
|
return (role.role_name && role.role_name.snakecase) ||
|
120
|
-
reading.
|
120
|
+
reading.text.gsub(/ *\{0\} */,'').gsub(' ','_').downcase
|
121
121
|
end
|
122
122
|
|
123
123
|
# debug "\tleading_adjective=#{(p=preferred_role_ref).leading_adjective}, role_name=#{role.role_name}, role player=#{role.concept.name}, trailing_adjective=#{p.trailing_adjective}"
|
@@ -161,7 +161,7 @@ module ActiveFacts
|
|
161
161
|
other_role_method = (one_to_one ? "" : "all_") +
|
162
162
|
fact_type.entity_type.name.snakecase +
|
163
163
|
as
|
164
|
-
binary_dump(role, role_name, role.concept, one_to_one, nil, nil, other_role_method)
|
164
|
+
binary_dump(role, role_name, role.concept, true, one_to_one, nil, nil, other_role_method)
|
165
165
|
}
|
166
166
|
end
|
167
167
|
|
@@ -65,7 +65,7 @@ module ActiveFacts
|
|
65
65
|
def value_types_dump
|
66
66
|
done_banner = false
|
67
67
|
@value_type_dumped = {}
|
68
|
-
@vocabulary.
|
68
|
+
@vocabulary.all_concept.sort_by{|o| o.name}.each{|o|
|
69
69
|
next unless o.is_a?(ActiveFacts::Metamodel::ValueType)
|
70
70
|
|
71
71
|
value_type_banner unless done_banner
|
@@ -90,11 +90,11 @@ module ActiveFacts
|
|
90
90
|
# if possible (it's not always, there may be loops!)
|
91
91
|
def entity_types_dump
|
92
92
|
# Build hash tables of precursors and followers to use:
|
93
|
-
precursors, followers = *build_entity_dependencies
|
93
|
+
@precursors, @followers = *build_entity_dependencies
|
94
94
|
|
95
95
|
done_banner = false
|
96
|
-
sorted = @vocabulary.
|
97
|
-
o.is_a?(ActiveFacts::Metamodel::EntityType) and !o.fact_type
|
96
|
+
sorted = @vocabulary.all_concept.select{|o|
|
97
|
+
o.is_a?(ActiveFacts::Metamodel::EntityType) # and !o.fact_type
|
98
98
|
}.sort_by{|o| o.name}
|
99
99
|
panic = nil
|
100
100
|
while true do
|
@@ -105,7 +105,7 @@ module ActiveFacts
|
|
105
105
|
|
106
106
|
# Can we do this yet?
|
107
107
|
if (o != panic and # We don't *have* to do it (panic mode)
|
108
|
-
(p = precursors[o]) and # There might be...
|
108
|
+
(p = @precursors[o]) and # There might be...
|
109
109
|
p.size > 0) # precursors - still blocked
|
110
110
|
skipped_this_pass += 1
|
111
111
|
next
|
@@ -115,14 +115,19 @@ module ActiveFacts
|
|
115
115
|
done_banner = true
|
116
116
|
|
117
117
|
# We're going to emit o - remove it from precursors of others:
|
118
|
-
(followers[o]||[]).each{|f|
|
119
|
-
precursors[f] -= [o]
|
118
|
+
(@followers[o]||[]).each{|f|
|
119
|
+
@precursors[f] -= [o]
|
120
120
|
}
|
121
121
|
count_this_pass += 1
|
122
122
|
panic = nil
|
123
123
|
|
124
|
-
|
125
|
-
|
124
|
+
if (o.fact_type)
|
125
|
+
fact_type_dump_with_dependents(o.fact_type)
|
126
|
+
released_fact_types_dump(o)
|
127
|
+
else
|
128
|
+
entity_type_dump(o)
|
129
|
+
released_fact_types_dump(o)
|
130
|
+
end
|
126
131
|
|
127
132
|
entity_type_group_end
|
128
133
|
}
|
@@ -136,7 +141,7 @@ module ActiveFacts
|
|
136
141
|
":\n\t" + bad.map{|o|
|
137
142
|
o.name +
|
138
143
|
": " +
|
139
|
-
precursors[o].map{|p| p.name}.uniq.inspect
|
144
|
+
@precursors[o].map{|p| p.name}.uniq.inspect
|
140
145
|
} * "\n\t" + "\n"
|
141
146
|
else
|
142
147
|
# Find the object that has the most followers and no fwd-ref'd supertypes:
|
@@ -144,7 +149,7 @@ module ActiveFacts
|
|
144
149
|
panic = sorted.
|
145
150
|
select{|o| !@concept_types_dumped[o] }.
|
146
151
|
sort_by{|o|
|
147
|
-
f = followers[o] || [];
|
152
|
+
f = @followers[o] || [];
|
148
153
|
o.supertypes.detect{|s| !@concept_types_dumped[s] } ? 0 : -f.size
|
149
154
|
}[0]
|
150
155
|
# debug "Panic mode, selected #{panic.name} next"
|
@@ -179,7 +184,7 @@ module ActiveFacts
|
|
179
184
|
role_refs = pi.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
180
185
|
|
181
186
|
# We need to get the adjectives for the roles from the identifying fact's preferred readings:
|
182
|
-
identifying_facts = role_refs.map{|rr| rr.role.fact_type }.uniq
|
187
|
+
identifying_facts = ([o.fact_type]+role_refs.map{|rr| rr.role.fact_type }).compact.uniq
|
183
188
|
preferred_readings = identifying_facts.inject({}){|reading_hash, fact_type|
|
184
189
|
pr = fact_type.preferred_reading
|
185
190
|
reading_hash[fact_type] = pr
|
@@ -211,12 +216,13 @@ module ActiveFacts
|
|
211
216
|
def expanded_reading(reading, fact_constraints, define_role_names)
|
212
217
|
# Find all role numbers in order of occurrence in this reading:
|
213
218
|
role_refs = reading.role_sequence.all_role_ref.sort_by{|role_ref| role_ref.ordinal}
|
214
|
-
role_numbers = reading.
|
219
|
+
role_numbers = reading.text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
|
215
220
|
roles = role_numbers.map{|m| role_refs[m].role }
|
216
|
-
# debug "Considering #{reading.
|
221
|
+
# debug "Considering #{reading.text} having #{role_numbers.inspect}"
|
217
222
|
|
218
223
|
# Find the constraints that constrain frequency over each role we can verbalise:
|
219
224
|
frequency_constraints = []
|
225
|
+
value_restrictions = []
|
220
226
|
roles.each do |role|
|
221
227
|
# Find a mandatory constraint that's *not* unique; this will need an extra reading
|
222
228
|
role_is_first_in = reading.fact_type.all_reading.detect{|r|
|
@@ -225,6 +231,15 @@ module ActiveFacts
|
|
225
231
|
}[0].role
|
226
232
|
}
|
227
233
|
|
234
|
+
if vr = role.role_value_restriction
|
235
|
+
if @constraints_used[vr]
|
236
|
+
vr = nil
|
237
|
+
else
|
238
|
+
@constraints_used[vr] = true
|
239
|
+
vr = vr.describe
|
240
|
+
end
|
241
|
+
end
|
242
|
+
value_restrictions << vr
|
228
243
|
if (role == roles.last) # First role of the reading?
|
229
244
|
# REVISIT: With a ternary, doing this on other than the last role can be ambiguous,
|
230
245
|
# in case both the 2nd and 3rd roles have frequencies. Think some more!
|
@@ -245,7 +260,7 @@ module ActiveFacts
|
|
245
260
|
end
|
246
261
|
end
|
247
262
|
|
248
|
-
expanded = reading.expand(frequency_constraints, define_role_names)
|
263
|
+
expanded = reading.expand(frequency_constraints, define_role_names, value_restrictions)
|
249
264
|
|
250
265
|
if (ft_rings = @ring_constraints_by_fact[reading.fact_type]) &&
|
251
266
|
(ring = ft_rings.detect{|rc| !@constraints_used[rc]})
|
@@ -279,8 +294,8 @@ module ActiveFacts
|
|
279
294
|
# This returns an array of two hash tables each keyed by an EntityType.
|
280
295
|
# The values of each hash entry are the precursors and followers (respectively) of that entity.
|
281
296
|
def build_entity_dependencies
|
282
|
-
@vocabulary.
|
283
|
-
if o.is_a?(ActiveFacts::Metamodel::EntityType)
|
297
|
+
@vocabulary.all_concept.inject([{},{}]) { |a, o|
|
298
|
+
if o.is_a?(ActiveFacts::Metamodel::EntityType)
|
284
299
|
precursor = a[0]
|
285
300
|
follower = a[1]
|
286
301
|
blocked = false
|
@@ -289,12 +304,21 @@ module ActiveFacts
|
|
289
304
|
pi.role_sequence.all_role_ref.each{|rr|
|
290
305
|
role = rr.role
|
291
306
|
player = role.concept
|
307
|
+
# REVISIT: If we decide to emit value types on demand, need to remove this:
|
292
308
|
next unless player.is_a?(ActiveFacts::Metamodel::EntityType)
|
293
309
|
# player is a precursor of o
|
294
310
|
(precursor[o] ||= []) << player if (player != o)
|
295
311
|
(follower[player] ||= []) << o if (player != o)
|
296
312
|
}
|
297
313
|
end
|
314
|
+
if o.fact_type
|
315
|
+
o.fact_type.all_role.each do |role|
|
316
|
+
next unless role.concept.is_a?(ActiveFacts::Metamodel::EntityType)
|
317
|
+
(precursor[o] ||= []) << role.concept
|
318
|
+
(follower[role.concept] ||= []) << o
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
298
322
|
# Supertypes are precursors too:
|
299
323
|
subtyping = o.all_type_inheritance_as_supertype
|
300
324
|
next a if subtyping.size == 0
|
@@ -304,6 +328,12 @@ module ActiveFacts
|
|
304
328
|
(precursor[s] ||= []) << o
|
305
329
|
(follower[o] ||= []) << s
|
306
330
|
}
|
331
|
+
# REVISIT: Need to use this to order ValueTypes after their supertypes
|
332
|
+
# else
|
333
|
+
# o.all_value_type_as_supertype.each { |s|
|
334
|
+
# (precursor[s] ||= []) << o
|
335
|
+
# (follower[o] ||= []) << s
|
336
|
+
# }
|
307
337
|
end
|
308
338
|
a
|
309
339
|
}
|
@@ -317,7 +347,9 @@ module ActiveFacts
|
|
317
347
|
roles.map(&:fact_type).uniq.select{|fact_type|
|
318
348
|
# The fact type hasn't already been dumped but all its role players have
|
319
349
|
!@fact_types_dumped[fact_type] &&
|
320
|
-
!fact_type.all_role.detect{|r| !@concept_types_dumped[r.concept] }
|
350
|
+
!fact_type.all_role.detect{|r| !@concept_types_dumped[r.concept] } &&
|
351
|
+
!fact_type.entity_type
|
352
|
+
# !(fact_type.entity_type && (p = @precursors[fact_type.entity_type]) && p.size > 0)
|
321
353
|
}.sort_by{|fact_type|
|
322
354
|
fact_type_key(fact_type)
|
323
355
|
}.each{|fact_type|
|
@@ -380,7 +412,7 @@ module ActiveFacts
|
|
380
412
|
all_role_ref.
|
381
413
|
sort_by{|role_ref| role_ref.ordinal}.
|
382
414
|
map{|role_ref| [ role_ref.leading_adjective, role_ref.role.concept.name, role_ref.trailing_adjective ].compact*"-" } +
|
383
|
-
[pr.
|
415
|
+
[pr.text]
|
384
416
|
else
|
385
417
|
fact_type.all_role.map{|role| role.concept.name }
|
386
418
|
end
|
@@ -52,8 +52,8 @@ module ActiveFacts
|
|
52
52
|
|
53
53
|
return if !o.supertype && !is_special_supertype
|
54
54
|
if o.supertype && o.name == o.supertype.name
|
55
|
-
# In ActiveFacts, parameterising a ValueType will create a new
|
56
|
-
# throw Can't handle parameterized value type of same name as its
|
55
|
+
# In ActiveFacts, parameterising a ValueType will create a new ValueType
|
56
|
+
# throw Can't handle parameterized value type of same name as its ValueType" if ...
|
57
57
|
end
|
58
58
|
|
59
59
|
length = (l = o.length) && l > 0 ? ":length => #{l}" : nil
|
@@ -76,7 +76,7 @@ module ActiveFacts
|
|
76
76
|
if @sql and o.is_table
|
77
77
|
puts " table"
|
78
78
|
end
|
79
|
-
puts "
|
79
|
+
puts " restrict #{o.value_restriction.all_allowed_range_sorted.map{|ar| ar.to_s}*", "}\n" if o.value_restriction
|
80
80
|
puts " \# REVISIT: #{o.name} is in units of #{o.unit.name}\n" if o.unit
|
81
81
|
roles_dump(o)
|
82
82
|
puts " end\n\n"
|
@@ -147,26 +147,28 @@ module ActiveFacts
|
|
147
147
|
puts " maybe :"+role_name
|
148
148
|
end
|
149
149
|
|
150
|
-
def binary_dump(role, role_name, role_player, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
|
150
|
+
def binary_dump(role, role_name, role_player, mandatory = nil, one_to_one = nil, readings = nil, other_role_name = nil, other_method_name = nil)
|
151
151
|
# Find whether we need the name of the other role player, and whether it's defined yet:
|
152
152
|
if role_name.camelcase(true) == role_player.name.sub(/^[a-z]/) {|i| i.upcase}
|
153
153
|
# Don't use Class name if implied by rolename
|
154
154
|
role_reference = nil
|
155
155
|
else
|
156
|
-
role_reference = concept_reference(role_player)
|
156
|
+
role_reference = ":class => "+concept_reference(role_player)
|
157
157
|
end
|
158
|
-
other_role_name = ":"+other_role_name if other_role_name
|
158
|
+
other_role_name = ":counterpart => :"+other_role_name if other_role_name
|
159
159
|
|
160
160
|
line = " #{one_to_one ? "one_to_one" : "has_one" } " +
|
161
161
|
[ ":"+role_name,
|
162
162
|
role_reference,
|
163
|
+
mandatory ? ":mandatory => true" : nil,
|
163
164
|
readings,
|
164
|
-
other_role_name
|
165
|
+
other_role_name,
|
166
|
+
(vr = role.role_value_restriction) ? ":restrict => #{vr}" : nil
|
165
167
|
].compact*", "+" "
|
166
168
|
line += " "*(48-line.length) if line.length < 48
|
167
169
|
line += "\# See #{role_player.name}.#{other_method_name}" if other_method_name
|
168
170
|
puts line
|
169
|
-
puts " \# REVISIT: #{other_role_name} has restricted
|
171
|
+
#puts " \# REVISIT: #{other_role_name} has values restricted to #{role.role_value_restriction}\n" if role.role_value_restriction
|
170
172
|
end
|
171
173
|
|
172
174
|
def concept_reference concept
|
@@ -15,7 +15,7 @@ module ActiveFacts
|
|
15
15
|
# afgen --sql/mysql[=options] <file>.cql
|
16
16
|
# Options are comma or space separated:
|
17
17
|
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
-
# * norma Translate
|
18
|
+
# * norma Translate valuetypes from NORMA to SQL Server
|
19
19
|
class MYSQL
|
20
20
|
private
|
21
21
|
include Persistence
|
@@ -227,24 +227,28 @@ module ActiveFacts
|
|
227
227
|
end
|
228
228
|
|
229
229
|
private
|
230
|
+
def sql_value(value)
|
231
|
+
value.is_a_string ? sql_string(value.literal) : value.literal
|
232
|
+
end
|
233
|
+
|
234
|
+
def sql_string(str)
|
235
|
+
"'" + str.gsub(/'/,"''") + "'"
|
236
|
+
end
|
237
|
+
|
230
238
|
def check_clause(column_name, restrictions)
|
231
239
|
return "" if restrictions.empty?
|
232
240
|
# REVISIT: Merge all restrictions (later; now just use the first)
|
233
241
|
" CHECK(" +
|
234
|
-
restrictions[0].
|
235
|
-
# Put the allowed ranges into a defined order:
|
236
|
-
((min = ar.value_range.minimum_bound) && min.value) ||
|
237
|
-
((max = ar.value_range.maximum_bound) && max.value)
|
238
|
-
end.map do |ar|
|
242
|
+
restrictions[0].all_allowed_range_sorted.map do |ar|
|
239
243
|
vr = ar.value_range
|
240
244
|
min = vr.minimum_bound
|
241
245
|
max = vr.maximum_bound
|
242
|
-
if (min && max && max.value == min.value)
|
243
|
-
"#{column_name} = #{min.value}"
|
246
|
+
if (min && max && max.value.literal == min.value.literal)
|
247
|
+
"#{column_name} = #{sql_value(min.value)}"
|
244
248
|
else
|
245
249
|
inequalities = [
|
246
|
-
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{min.value}",
|
247
|
-
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{max.value}"
|
250
|
+
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
|
251
|
+
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
|
248
252
|
].compact
|
249
253
|
inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
|
250
254
|
end
|
@@ -15,7 +15,7 @@ module ActiveFacts
|
|
15
15
|
# afgen --sql/server[=options] <file>.cql
|
16
16
|
# Options are comma or space separated:
|
17
17
|
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
-
# * norma Translate
|
18
|
+
# * norma Translate valuetypes from NORMA to SQL Server
|
19
19
|
class SERVER
|
20
20
|
private
|
21
21
|
include Persistence
|
@@ -49,6 +49,7 @@ module ActiveFacts
|
|
49
49
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
50
50
|
@delay_fks = options.include? "delay_fks"
|
51
51
|
@norma = options.include? "norma"
|
52
|
+
@underscore = options.include?("underscore") ? "_" : ""
|
52
53
|
end
|
53
54
|
|
54
55
|
def puts s
|
@@ -102,7 +103,7 @@ module ActiveFacts
|
|
102
103
|
when "PictureRawData"; "image"
|
103
104
|
when "VariableLengthRawData"; "varbinary"
|
104
105
|
when "BIT"; "bit"
|
105
|
-
else raise "SQL type unknown for NORMA type #{type}"
|
106
|
+
else type # raise "SQL type unknown for NORMA type #{type}"
|
106
107
|
end
|
107
108
|
[sql_type, length]
|
108
109
|
end
|
@@ -116,7 +117,7 @@ module ActiveFacts
|
|
116
117
|
delayed_foreign_keys = []
|
117
118
|
|
118
119
|
@vocabulary.tables.each do |table|
|
119
|
-
puts "CREATE TABLE #{escape table.name} ("
|
120
|
+
puts "CREATE TABLE #{escape table.name(@underscore)} ("
|
120
121
|
|
121
122
|
pk = table.identifier_columns
|
122
123
|
identity_column = pk[0] if pk.size == 1 && pk[0].is_auto_assigned
|
@@ -128,8 +129,8 @@ module ActiveFacts
|
|
128
129
|
|
129
130
|
# We sort the columns here, not in the persistence layer, because it affects
|
130
131
|
# the ordering of columns in an index :-(.
|
131
|
-
columns = table.columns.sort_by { |column| column.name(
|
132
|
-
name = escape column.name(
|
132
|
+
columns = table.columns.sort_by { |column| column.name(@underscore) }.map do |column|
|
133
|
+
name = escape column.name(@underscore)
|
133
134
|
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
134
135
|
type, params, restrictions = column.type
|
135
136
|
restrictions = [] if (fk_columns.include?(column)) # Don't enforce VT restrictions on FK columns
|
@@ -153,22 +154,22 @@ module ActiveFacts
|
|
153
154
|
end.flatten
|
154
155
|
|
155
156
|
pk_def = (pk.detect{|column| !column.is_mandatory} ? "UNIQUE(" : "PRIMARY KEY(") +
|
156
|
-
pk.map{|column| escape column.name(
|
157
|
+
pk.map{|column| escape column.name(@underscore)}*", " +
|
157
158
|
")"
|
158
159
|
|
159
160
|
inline_fks = []
|
160
161
|
table.foreign_keys.each do |fk|
|
161
162
|
fk_text = "FOREIGN KEY (" +
|
162
|
-
fk.from_columns.map{|column| column.name}*", " +
|
163
|
-
") REFERENCES #{escape fk.to.name} (" +
|
164
|
-
fk.to_columns.map{|column| column.name}*", " +
|
163
|
+
fk.from_columns.map{|column| column.name(@underscore)}*", " +
|
164
|
+
") REFERENCES #{escape fk.to.name(@underscore)} (" +
|
165
|
+
fk.to_columns.map{|column| column.name(@underscore)}*", " +
|
165
166
|
")"
|
166
167
|
if !@delay_fks and # We don't want to delay all Fks
|
167
168
|
(tables_emitted[fk.to] or # The target table has been emitted
|
168
169
|
fk.to == table && !fk.to_columns.detect{|column| !column.is_mandatory}) # The reference columns already have the required indexes
|
169
170
|
inline_fks << fk_text
|
170
171
|
else
|
171
|
-
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name}\n\tADD " + fk_text)
|
172
|
+
delayed_foreign_keys << ("ALTER TABLE #{escape fk.from.name(@underscore)}\n\tADD " + fk_text)
|
172
173
|
end
|
173
174
|
end
|
174
175
|
|
@@ -177,8 +178,8 @@ module ActiveFacts
|
|
177
178
|
delayed_indices = []
|
178
179
|
indices.each do |index|
|
179
180
|
next if index.over == table && index.is_primary # Already did the primary keys
|
180
|
-
abbreviated_column_names = index.abbreviated_column_names*""
|
181
|
-
column_names = index.column_names
|
181
|
+
abbreviated_column_names = index.abbreviated_column_names(@underscore)*""
|
182
|
+
column_names = index.column_names(@underscore)
|
182
183
|
column_name_list = column_names.map{|n| escape(n)}*", "
|
183
184
|
if index.columns.all?{|column| column.is_mandatory}
|
184
185
|
inline_indices << "UNIQUE(#{column_name_list})"
|
@@ -186,17 +187,17 @@ module ActiveFacts
|
|
186
187
|
view_name = escape "#{index.view_name}_#{abbreviated_column_names}"
|
187
188
|
delayed_indices <<
|
188
189
|
%Q{CREATE VIEW dbo.#{view_name} (#{column_name_list}) WITH SCHEMABINDING AS
|
189
|
-
\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name}
|
190
|
+
\tSELECT #{column_name_list} FROM dbo.#{escape index.on.name(@underscore)}
|
190
191
|
\tWHERE\t#{
|
191
192
|
index.columns.
|
192
193
|
select{|column| !column.is_mandatory }.
|
193
194
|
map{|column|
|
194
|
-
escape(column.name) + " IS NOT NULL"
|
195
|
+
escape(column.name(@underscore)) + " IS NOT NULL"
|
195
196
|
}*"\n\t AND\t"
|
196
197
|
}
|
197
198
|
GO
|
198
199
|
|
199
|
-
CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.columns.map{|column| column.name}*", "})
|
200
|
+
CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.columns.map{|column| column.name(@underscore)}*", "})
|
200
201
|
}
|
201
202
|
end
|
202
203
|
end
|
@@ -216,24 +217,28 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
|
|
216
217
|
end
|
217
218
|
|
218
219
|
private
|
220
|
+
def sql_value(value)
|
221
|
+
value.is_a_string ? sql_string(value.literal) : value.literal
|
222
|
+
end
|
223
|
+
|
224
|
+
def sql_string(str)
|
225
|
+
"'" + str.gsub(/'/,"''") + "'"
|
226
|
+
end
|
227
|
+
|
219
228
|
def check_clause(column_name, restrictions)
|
220
229
|
return "" if restrictions.empty?
|
221
230
|
# REVISIT: Merge all restrictions (later; now just use the first)
|
222
231
|
" CHECK(" +
|
223
|
-
restrictions[0].
|
224
|
-
# Put the allowed ranges into a defined order:
|
225
|
-
((min = ar.value_range.minimum_bound) && min.value) ||
|
226
|
-
((max = ar.value_range.maximum_bound) && max.value)
|
227
|
-
end.map do |ar|
|
232
|
+
restrictions[0].all_allowed_range_sorted.map do |ar|
|
228
233
|
vr = ar.value_range
|
229
234
|
min = vr.minimum_bound
|
230
235
|
max = vr.maximum_bound
|
231
|
-
if (min && max && max.value == min.value)
|
232
|
-
"#{column_name} = #{min.value}"
|
236
|
+
if (min && max && max.value.literal == min.value.literal)
|
237
|
+
"#{column_name} = #{sql_value(min.value)}"
|
233
238
|
else
|
234
239
|
inequalities = [
|
235
|
-
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{min.value}",
|
236
|
-
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{max.value}"
|
240
|
+
min && "#{column_name} >#{min.is_inclusive ? "=" : ""} #{sql_value(min.value)}",
|
241
|
+
max && "#{column_name} <#{max.is_inclusive ? "=" : ""} #{sql_value(max.value)}"
|
237
242
|
].compact
|
238
243
|
inequalities.size > 1 ? "(" + inequalities*" AND " + ")" : inequalities[0]
|
239
244
|
end
|