activefacts 0.7.3 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|