activefacts 0.7.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/LICENSE +19 -0
  2. data/Manifest.txt +24 -2
  3. data/Rakefile +25 -3
  4. data/bin/afgen +1 -1
  5. data/bin/cql +13 -2
  6. data/css/offline.css +3 -0
  7. data/css/orm2.css +24 -0
  8. data/css/print.css +8 -0
  9. data/css/style-print.css +357 -0
  10. data/css/style.css +387 -0
  11. data/download.html +85 -0
  12. data/examples/CQL/Address.cql +3 -3
  13. data/examples/CQL/Blog.cql +13 -14
  14. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  15. data/examples/CQL/Death.cql +3 -2
  16. data/examples/CQL/Genealogy.cql +13 -11
  17. data/examples/CQL/Marriage.cql +2 -2
  18. data/examples/CQL/Metamodel.cql +136 -93
  19. data/examples/CQL/MultiInheritance.cql +2 -2
  20. data/examples/CQL/OilSupply.cql +14 -10
  21. data/examples/CQL/Orienteering.cql +22 -19
  22. data/examples/CQL/PersonPlaysGame.cql +3 -2
  23. data/examples/CQL/SchoolActivities.cql +4 -2
  24. data/examples/CQL/SimplestUnary.cql +1 -1
  25. data/examples/CQL/SubtypePI.cql +6 -7
  26. data/examples/CQL/Warehousing.cql +16 -19
  27. data/examples/CQL/unit.cql +584 -0
  28. data/examples/index.html +276 -0
  29. data/examples/intro.html +497 -0
  30. data/examples/local.css +20 -0
  31. data/index.html +96 -0
  32. data/lib/activefacts/api/concept.rb +48 -46
  33. data/lib/activefacts/api/constellation.rb +43 -23
  34. data/lib/activefacts/api/entity.rb +2 -2
  35. data/lib/activefacts/api/instance.rb +6 -2
  36. data/lib/activefacts/api/instance_index.rb +5 -0
  37. data/lib/activefacts/api/value.rb +8 -2
  38. data/lib/activefacts/api/vocabulary.rb +15 -10
  39. data/lib/activefacts/cql/CQLParser.treetop +109 -88
  40. data/lib/activefacts/cql/Concepts.treetop +32 -10
  41. data/lib/activefacts/cql/Context.treetop +34 -0
  42. data/lib/activefacts/cql/Expressions.treetop +9 -9
  43. data/lib/activefacts/cql/FactTypes.treetop +30 -31
  44. data/lib/activefacts/cql/Language/English.treetop +50 -0
  45. data/lib/activefacts/cql/LexicalRules.treetop +2 -1
  46. data/lib/activefacts/cql/Terms.treetop +117 -0
  47. data/lib/activefacts/cql/ValueTypes.treetop +152 -0
  48. data/lib/activefacts/cql/compiler.rb +1718 -0
  49. data/lib/activefacts/cql/parser.rb +124 -57
  50. data/lib/activefacts/generate/absorption.rb +1 -1
  51. data/lib/activefacts/generate/cql.rb +111 -100
  52. data/lib/activefacts/generate/cql/html.rb +5 -5
  53. data/lib/activefacts/generate/oo.rb +3 -3
  54. data/lib/activefacts/generate/ordered.rb +51 -19
  55. data/lib/activefacts/generate/ruby.rb +10 -8
  56. data/lib/activefacts/generate/sql/mysql.rb +14 -10
  57. data/lib/activefacts/generate/sql/server.rb +29 -24
  58. data/lib/activefacts/input/cql.rb +9 -1264
  59. data/lib/activefacts/input/orm.rb +213 -200
  60. data/lib/activefacts/persistence/columns.rb +11 -10
  61. data/lib/activefacts/persistence/index.rb +15 -18
  62. data/lib/activefacts/persistence/reference.rb +17 -17
  63. data/lib/activefacts/persistence/tables.rb +50 -51
  64. data/lib/activefacts/version.rb +1 -1
  65. data/lib/activefacts/vocabulary/extensions.rb +79 -8
  66. data/lib/activefacts/vocabulary/metamodel.rb +183 -114
  67. data/spec/absorption_ruby_spec.rb +99 -0
  68. data/spec/absorption_spec.rb +3 -4
  69. data/spec/api/constellation.rb +1 -1
  70. data/spec/api/entity_type.rb +3 -1
  71. data/spec/api/instance.rb +4 -2
  72. data/spec/api/roles.rb +8 -6
  73. data/spec/api_spec.rb +1 -2
  74. data/spec/cql/context_spec.rb +71 -0
  75. data/spec/cql/samples_spec.rb +154 -0
  76. data/spec/cql/unit_spec.rb +375 -0
  77. data/spec/cql_cql_spec.rb +31 -21
  78. data/spec/cql_mysql_spec.rb +70 -0
  79. data/spec/cql_parse_spec.rb +15 -9
  80. data/spec/cql_ruby_spec.rb +27 -13
  81. data/spec/cql_sql_spec.rb +42 -16
  82. data/spec/cql_symbol_tables_spec.rb +2 -3
  83. data/spec/cqldump_spec.rb +7 -7
  84. data/spec/helpers/file_matcher.rb +39 -0
  85. data/spec/norma_cql_spec.rb +20 -12
  86. data/spec/norma_ruby_spec.rb +6 -3
  87. data/spec/norma_sql_spec.rb +6 -3
  88. data/spec/norma_tables_spec.rb +6 -4
  89. data/spec/spec_helper.rb +27 -8
  90. data/status.html +69 -0
  91. data/why.html +60 -0
  92. metadata +34 -11
  93. data/lib/activefacts/cql/DataTypes.treetop +0 -81
  94. 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 datatype
66
- # throw Can't handle parameterized value type of same name as its datatype" if ...
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.reading_text =~ /^\{(\d)\} has \{\d\}$/
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.reading_text =~ /^\{(\d)\} is of \{\d\}$/
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.reading_text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
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.reading_text.gsub(/ *\{0\} */,'').gsub(' ','_').downcase
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.all_feature.sort_by{|o| o.name}.each{|o|
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.all_feature.select{|o|
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
- entity_type_dump(o)
125
- released_fact_types_dump(o)
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.reading_text.scan(/\{(\d)\}/).flatten.map{|m| Integer(m) }
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.reading_text} having #{role_numbers.inspect}"
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.all_feature.inject([{},{}]) { |a, o|
283
- if o.is_a?(ActiveFacts::Metamodel::EntityType) && !o.fact_type
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.reading_text]
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 datatype
56
- # throw Can't handle parameterized value type of same name as its datatype" if ...
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 " \# REVISIT: #{o.name} has restricted values\n" if o.value_restriction
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 values\n" if role.role_value_restriction
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 datatypes from NORMA to SQL Server
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].all_allowed_range.sort_by do |ar|
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 datatypes from NORMA to SQL Server
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(nil) }.map do |column|
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].all_allowed_range.sort_by do |ar|
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