activefacts 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -13
  2. data/Rakefile +2 -2
  3. data/bin/afgen +1 -1
  4. data/bin/cql +118 -27
  5. data/examples/CQL/Insurance.cql +2 -2
  6. data/examples/CQL/Metamodel.cql +3 -3
  7. data/examples/CQL/SchoolActivities.cql +1 -1
  8. data/examples/CQL/Warehousing.cql +5 -4
  9. data/lib/activefacts/cql.rb +1 -1
  10. data/lib/activefacts/cql/Language/English.treetop +2 -1
  11. data/lib/activefacts/cql/compiler.rb +6 -6
  12. data/lib/activefacts/cql/compiler/clause.rb +69 -46
  13. data/lib/activefacts/cql/compiler/constraint.rb +14 -14
  14. data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
  15. data/lib/activefacts/cql/compiler/fact.rb +40 -27
  16. data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
  17. data/lib/activefacts/cql/compiler/query.rb +12 -12
  18. data/lib/activefacts/cql/compiler/shared.rb +9 -0
  19. data/lib/activefacts/cql/compiler/value_type.rb +4 -4
  20. data/lib/activefacts/cql/parser.rb +9 -9
  21. data/lib/activefacts/generate/cql.rb +41 -20
  22. data/lib/activefacts/generate/helpers/oo.rb +33 -70
  23. data/lib/activefacts/generate/helpers/ordered.rb +61 -87
  24. data/lib/activefacts/generate/ruby.rb +12 -72
  25. data/lib/activefacts/generate/transform/surrogate.rb +13 -13
  26. data/lib/activefacts/input/orm.rb +72 -71
  27. data/lib/activefacts/persistence/columns.rb +66 -31
  28. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  29. data/lib/activefacts/persistence/index.rb +12 -12
  30. data/lib/activefacts/persistence/object_type.rb +15 -12
  31. data/lib/activefacts/persistence/reference.rb +20 -18
  32. data/lib/activefacts/persistence/tables.rb +40 -36
  33. data/lib/activefacts/support.rb +69 -123
  34. data/lib/activefacts/version.rb +2 -2
  35. data/lib/activefacts/vocabulary/extensions.rb +42 -39
  36. data/lib/activefacts/vocabulary/metamodel.rb +11 -1
  37. data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
  38. data/spec/cql/contractions_spec.rb +1 -1
  39. data/spec/cql/entity_type_spec.rb +1 -1
  40. data/spec/cql/fact_type_matching_spec.rb +3 -3
  41. data/spec/cql/role_matching_spec.rb +4 -4
  42. data/spec/cql/samples_spec.rb +2 -2
  43. data/spec/cql_cql_spec.rb +1 -1
  44. data/spec/helpers/array_matcher.rb +1 -1
  45. data/spec/norma_ruby_sql_spec.rb +2 -2
  46. data/spec/norma_tables_spec.rb +3 -2
  47. metadata +47 -68
@@ -29,14 +29,14 @@ module ActiveFacts
29
29
 
30
30
  # Always a table if marked so:
31
31
  if is_independent
32
- debug :absorption, "ValueType #{name} is declared independent"
32
+ trace :absorption, "ValueType #{name} is declared independent"
33
33
  @tentative = false
34
34
  return @is_table = true
35
35
  end
36
36
 
37
37
  # Only a table if it has references (to another ValueType)
38
38
  if !references_from.empty? && !is_auto_assigned
39
- debug :absorption, "#{name} is a table because it has #{references_from.size} references to it"
39
+ trace :absorption, "#{name} is a table because it has #{references_from.size} references to it"
40
40
  @is_table = true
41
41
  else
42
42
  @is_table = false
@@ -57,10 +57,8 @@ module ActiveFacts
57
57
 
58
58
  class EntityType < DomainObjectType
59
59
  # A Reference from an entity type that fully absorbs this one
60
- def absorbed_via; @absorbed_via; end
61
- def absorbed_via=(r) #:nodoc:
62
- @absorbed_via = r
63
- end
60
+ attr_accessor :absorbed_via #:nodoc:
61
+ attr_accessor :absorbed_mirror #:nodoc:
64
62
 
65
63
  def is_auto_assigned #:nodoc:
66
64
  false
@@ -74,14 +72,14 @@ module ActiveFacts
74
72
 
75
73
  # Always a table if marked so
76
74
  if is_independent
77
- debug :absorption, "EntityType #{name} is declared independent"
75
+ trace :absorption, "EntityType #{name} is declared independent"
78
76
  return @is_table = true
79
77
  end
80
78
 
81
79
  # Always a table if nowhere else to go, and has no one-to-ones that might flip:
82
80
  if references_to.empty? and
83
81
  !references_from.detect{|ref| ref.role_type == :one_one }
84
- debug :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
82
+ trace :absorption, "EntityType #{name} is presumed independent as it has nowhere to go"
85
83
  return @is_table = true
86
84
  end
87
85
 
@@ -92,11 +90,11 @@ module ActiveFacts
92
90
  as_ti = all_supertype_inheritance.detect{|ti| ti.assimilation}
93
91
  @is_table = as_ti != nil
94
92
  if @is_table
95
- debug :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
93
+ trace :absorption, "EntityType #{name} is #{as_ti.assimilation} from supertype #{as_ti.supertype}"
96
94
  else
97
95
  identifying_fact_type = preferred_identifier.role_sequence.all_role_ref.to_a[0].role.fact_type
98
96
  if identifying_fact_type.is_a?(TypeInheritance)
99
- debug :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
97
+ trace :absorption, "EntityType #{name} is absorbed into supertype #{supertypes[0].name}"
100
98
  @is_table = false
101
99
  else
102
100
  # Possibly absorbed, we'll have to see how that pans out
@@ -114,7 +112,7 @@ module ActiveFacts
114
112
  next false unless rr.role.object_type.is_a? ValueType
115
113
  rr.role.object_type.is_auto_assigned
116
114
  }
117
- debug :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
115
+ trace :absorption, "#{name} has an auto-assigned counter in its ID, so must be a table"
118
116
  @tentative = false
119
117
  return @is_table = true
120
118
  end
@@ -215,7 +213,7 @@ module ActiveFacts
215
213
 
216
214
  populate_all_references
217
215
 
218
- debug :absorption, "Calculating relational composition" do
216
+ trace :absorption, "Calculating relational composition" do
219
217
  # Evaluate the possible independence of each object_type, building an array of object_types of indeterminate status:
220
218
  undecided =
221
219
  all_object_type.select do |object_type|
@@ -223,11 +221,11 @@ module ActiveFacts
223
221
  object_type.tentative # Selection criterion
224
222
  end
225
223
 
226
- if debug :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
224
+ if trace :absorption, "Generating tables, #{undecided.size} undecided, already decided ones are"
227
225
  (all_object_type-undecided).each {|object_type|
228
226
  next if ValueType === object_type && !object_type.is_table # Skip unremarkable cases
229
- debug :absorption do
230
- debug :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
227
+ trace :absorption do
228
+ trace :absorption, "#{object_type.name} is #{object_type.is_table ? "" : "not "}a table#{object_type.tentative ? ", tentatively" : ""}"
231
229
  end
232
230
  }
233
231
  end
@@ -235,30 +233,34 @@ module ActiveFacts
235
233
  pass = 0
236
234
  begin # Loop while we continue to make progress
237
235
  pass += 1
238
- debug :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
236
+ trace :absorption, "Starting composition pass #{pass} with #{undecided.size} undecided tables"
239
237
  possible_flips = {} # A hash by table containing an array of references that can be flipped
240
238
  finalised = # Make an array of things we finalised during this pass
241
239
  undecided.select do |object_type|
242
- debug :absorption, "Considering #{object_type.name}:" do
243
- debug :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
244
- debug :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
240
+ trace :absorption, "Considering #{object_type.name}:" do
241
+ trace :absorption, "refs to #{object_type.name} are from #{object_type.references_to.map{|ref| ref.from.name}*", "}" if object_type.references_to.size > 0
242
+ trace :absorption, "refs from #{object_type.name} are to #{object_type.references_from.map{|ref| ref.to ? ref.to.name : ref.fact_type.default_reading}*", "}" if object_type.references_from.size > 0
245
243
 
246
244
  # Always absorb an objectified unary into its role player:
247
245
  if object_type.fact_type && object_type.fact_type.all_role.size == 1
248
- debug :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
246
+ trace :absorption, "Absorb objectified unary #{object_type.name} into #{object_type.fact_type.entity_type.name}"
249
247
  object_type.definitely_not_table
250
248
  next object_type
251
249
  end
252
250
 
253
251
  # If the PI contains one role only, played by an entity type that can absorb us, do that.
254
252
  pi_roles = object_type.preferred_identifier.role_sequence.all_role_ref.map(&:role)
255
- debug :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
253
+ trace :absorption, "pi_roles are played by #{pi_roles.map{|role| role.object_type.name}*", "}"
256
254
  first_pi_role = pi_roles[0]
257
255
  pi_ref = nil
258
256
  if pi_roles.size == 1 and
259
- object_type.references_to.detect{|ref| pi_ref = ref if ref.from_role == first_pi_role && ref.from.is_a?(EntityType)}
257
+ object_type.references_to.detect do |ref|
258
+ if ref.from_role == first_pi_role and ref.from.is_a?(EntityType) # and ref.is_mandatory # REVISIT
259
+ pi_ref = ref
260
+ end
261
+ end
260
262
 
261
- debug :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
263
+ trace :absorption, "#{object_type.name} is fully absorbed along its sole reference path into entity type #{pi_ref.from.name}"
262
264
  object_type.definitely_not_table
263
265
  next object_type
264
266
  end
@@ -268,7 +270,7 @@ module ActiveFacts
268
270
  object_type.references_from.reject{|ref|
269
271
  pi_roles.include?(ref.to_role)
270
272
  }
271
- debug :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
273
+ trace :absorption, "#{object_type.name} has #{non_identifying_refs_from.size} non-identifying functional roles"
272
274
 
273
275
  =begin
274
276
  # This is kinda arbitrary. We need a policy for evaluating optional flips, so we can decide if they "improve" things.
@@ -276,9 +278,9 @@ module ActiveFacts
276
278
 
277
279
  # If all non-identifying functional roles are one-to-ones that can be flipped, do that:
278
280
  if non_identifying_refs_from.all? { |ref| ref.role_type == :one_one && (ref.to.is_table || ref.to.tentative) }
279
- debug :absorption, "Flipping references from #{object_type.name}" do
281
+ trace :absorption, "Flipping references from #{object_type.name}" do
280
282
  non_identifying_refs_from.each do |ref|
281
- debug :absorption, "Flipping #{ref}"
283
+ trace :absorption, "Flipping #{ref}"
282
284
  ref.flip
283
285
  end
284
286
  end
@@ -288,7 +290,7 @@ module ActiveFacts
288
290
 
289
291
  if object_type.references_to.size > 1 and
290
292
  non_identifying_refs_from.size > 0
291
- debug :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
293
+ trace :absorption, "#{object_type.name} has non-identifying functional dependencies so 3NF requires it be a table"
292
294
  object_type.definitely_table
293
295
  next object_type
294
296
  end
@@ -306,15 +308,15 @@ module ActiveFacts
306
308
  to_is_mandatory = !ref.to_role || !!ref.to_role.is_mandatory
307
309
 
308
310
  bad = !(ref.from == object_type ? from_is_mandatory : to_is_mandatory)
309
- debug :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
311
+ trace :absorption, "Not absorbing #{object_type.name} through non-mandatory #{ref}" if bad
310
312
  bad
311
313
  end
312
314
 
313
315
  # If this object can be fully absorbed, do that (might require flipping some references)
314
316
  if absorption_paths.size > 0
315
- debug :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
317
+ trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
316
318
  absorption_paths.each do |ref|
317
- debug :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
319
+ trace :absorption, "Flipping #{ref} so #{object_type.name} can be absorbed"
318
320
  ref.flip if object_type == ref.from
319
321
  end
320
322
  object_type.definitely_not_table
@@ -322,10 +324,12 @@ module ActiveFacts
322
324
  end
323
325
 
324
326
  if non_identifying_refs_from.size == 0
327
+ # REVISIT: This allows absorption along a non-mandatory role of a objectified fact type
328
+ # and object_type.references_to.all?{|ref| ref.is_mandatory }
325
329
  # and (!object_type.is_a?(EntityType) ||
326
330
  # # REVISIT: The roles may be collectively but not individually mandatory.
327
331
  # object_type.references_to.detect { |ref| !ref.from_role || ref.from_role.is_mandatory })
328
- debug :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
332
+ trace :absorption, "#{object_type.name} is fully absorbed in #{object_type.references_to.size} places: #{object_type.references_to.map{|ref| ref.from.name}*", "}"
329
333
  object_type.definitely_not_table
330
334
  next object_type
331
335
  end
@@ -335,7 +339,7 @@ module ActiveFacts
335
339
  end
336
340
 
337
341
  undecided -= finalised
338
- debug :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
342
+ trace :absorption, "Finalised #{finalised.size} this pass: #{finalised.map{|f| f.name}*", "}"
339
343
  end while !finalised.empty?
340
344
 
341
345
  # A ValueType that isn't explicitly a table and isn't needed anywhere doesn't matter,
@@ -343,10 +347,10 @@ module ActiveFacts
343
347
  all_object_type.each do |object_type|
344
348
  if (!object_type.is_table and object_type.references_to.size == 0 and object_type.references_from.size > 0)
345
349
  if !object_type.references_from.detect{|r| !r.is_one_to_one || !r.to.is_table}
346
- debug :absorption, "Flipping references from #{object_type.name}; they're all to tables"
350
+ trace :absorption, "Flipping references from #{object_type.name}; they're all to tables"
347
351
  object_type.references_from.map(&:flip)
348
352
  else
349
- debug :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
353
+ trace :absorption, "Making #{object_type.name} a table; it has nowhere else to go and needs to absorb things"
350
354
  object_type.probably_table
351
355
  end
352
356
  end
@@ -354,9 +358,9 @@ module ActiveFacts
354
358
 
355
359
  # Now, evaluate all possibilities of the tentative assignments
356
360
  # Incomplete. Apparently unnecessary as well... so far. We'll see.
357
- if debug :absorption
361
+ if trace :absorption
358
362
  undecided.each do |object_type|
359
- debug :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
363
+ trace :absorption, "Unable to decide independence of #{object_type.name}, going with #{object_type.show_tabular}"
360
364
  end
361
365
  end
362
366
  end
@@ -1,108 +1,10 @@
1
1
  #
2
2
  # ActiveFacts Support code.
3
- # The debug method supports indented tracing.
4
- # Set the DEBUG environment variable to enable it. Search the code to find the DEBUG keywords, or use "all".
3
+ # The trace method supports indented tracing.
4
+ # Set the TRACE environment variable to enable it. Search the code to find the TRACE keywords, or use "all".
5
5
  #
6
6
  # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
7
7
  #
8
- #module ActiveFacts
9
- $debug_indent = 0
10
- $debug_nested = false # Set when a block enables all enclosed debugging
11
- $debug_keys = nil
12
- $debug_available = {}
13
-
14
- def debug_initialize
15
- # First time, initialise the tracing environment
16
- $debug_indent = 0
17
- unless $debug_keys
18
- $debug_keys = {}
19
- if (e = ENV["DEBUG"])
20
- e.split(/[^_a-zA-Z0-9]/).each{|k| debug_enable(k) }
21
- if $debug_keys[:help]
22
- at_exit {
23
- $stderr.puts "---\nDebugging keys available: #{$debug_available.keys.map{|s| s.to_s}.sort*", "}"
24
- }
25
- end
26
- end
27
- end
28
- end
29
-
30
- def debug_keys
31
- $debug_available.keys
32
- end
33
-
34
- def debug_enabled key
35
- !key.empty? && $debug_keys[key.to_sym]
36
- end
37
-
38
- def debug_enable key
39
- !key.empty? && $debug_keys[key.to_sym] = true
40
- end
41
-
42
- def debug_disable key
43
- !key.empty? && $debug_keys.delete(key.to_sym)
44
- end
45
-
46
- def debug_toggle key
47
- !key.empty? and debug_enabled(key) ? (debug_disable(key); false) : (debug_enable(key); true)
48
- end
49
-
50
- def debug_selected(args)
51
- # Figure out whether this trace is enabled (itself or by :all), if it nests, and if we should print the key:
52
- key =
53
- if Symbol === args[0]
54
- control = args.shift
55
- if (s = control.to_s) =~ /_\Z/
56
- nested = true
57
- s.sub(/_\Z/, '').to_sym # Avoid creating new strings willy-nilly
58
- else
59
- control
60
- end
61
- else
62
- :all
63
- end
64
-
65
- $debug_available[key] ||= key # Remember that this debug was requested, for help
66
- enabled = $debug_nested || # This debug is enabled because it's in a nested block
67
- $debug_keys[key] || # This debug is enabled in its own right
68
- $debug_keys[:all] # This debug is enabled because all are
69
- $debug_nested = nested
70
- [
71
- (enabled ? 1 : 0),
72
- $debug_keys[:all] ? " %-15s"%control : nil
73
- ]
74
- end
75
-
76
- def debug_show(*args)
77
- unless $debug_keys
78
- debug_initialize
79
- end
80
-
81
- enabled, key_to_show = debug_selected(args)
82
-
83
- # Emit the message if enabled or a parent is:
84
- if args.size > 0 && enabled == 1
85
- puts "\##{key_to_show} " +
86
- ' '*$debug_indent +
87
- args.
88
- # A laudable aim, certainly, but in practise the Procs leak and slow things down:
89
- # map{|a| a.respond_to?(:call) ? a.call : a}.
90
- join(' ')
91
- end
92
- $debug_indent += enabled
93
- enabled
94
- end
95
-
96
- def debug(*args, &block)
97
- begin
98
- old_indent, old_nested, enabled = $debug_indent, $debug_nested, debug_show(*args)
99
- return (block || proc { enabled == 1 }).call
100
- ensure
101
- $debug_indent, $debug_nested = old_indent, old_nested
102
- end
103
- end
104
-
105
- #end
106
8
 
107
9
  # Return all duplicate objects in the array (using hash-equality)
108
10
  class Array
@@ -155,34 +57,78 @@ class Array
155
57
  end
156
58
  end
157
59
 
158
- # Load the ruby debugger before everything else, if requested
159
- if debug :debug or debug :firstaid
160
- begin
161
- require 'ruby-debug'
162
- Debugger.start # (:post_mortem => true) # Some Ruby versions crash on post-mortem debugging
163
- rescue LoadError
164
- # Ok, no debugger, tough luck.
165
- end
60
+ class String
61
+ class Words
62
+ def initialize words
63
+ @words = words
64
+ end
166
65
 
167
- if debug :trap
168
- trap('SIGINT') do
169
- puts "Stopped at:\n\t"+caller*"\n\t"
170
- debugger
171
- true
66
+ def map(&b)
67
+ @words.map(&b)
68
+ end
69
+
70
+ def to_s
71
+ titlecase
72
+ end
73
+
74
+ def titlewords
75
+ @words.map do |word|
76
+ word[0].upcase+word[1..-1].downcase
77
+ end
78
+ end
79
+
80
+ def titlecase
81
+ titlewords.join('')
82
+ end
83
+
84
+ def capwords
85
+ @words.map do |word|
86
+ word[0].upcase+word[1..-1]
87
+ end
88
+ end
89
+
90
+ def capcase
91
+ capwords.join('')
92
+ end
93
+
94
+ def camelwords
95
+ count = 0
96
+ @words.map do |word|
97
+ if (count += 1) == 1
98
+ word
99
+ else
100
+ word[0].upcase+word[1..-1].downcase
101
+ end
102
+ end
172
103
  end
173
- end
174
104
 
175
- if debug :firstaid
176
- puts "Preparing first aid kit"
177
- class ::Exception
178
- alias_method :firstaid_initialize, :initialize
105
+ def camelcase
106
+ camelwords.join('')
107
+ end
179
108
 
180
- def initialize *args, &b
181
- send(:firstaid_initialize, *args, &b)
182
- puts "Stopped due to #{self.class}: #{message} at "+caller*"\n\t"
183
- debugger
184
- true # Exception thrown
109
+ def snakewords
110
+ @words.map do |w|
111
+ w.downcase
185
112
  end
186
113
  end
114
+
115
+ def snakecase
116
+ snakewords.join('_')
117
+ end
118
+
119
+ def to_a
120
+ @words
121
+ end
122
+
123
+ def +(words)
124
+ Words.new(@words + Array(words))
125
+ end
126
+ end
127
+
128
+ def words
129
+ Words.new(
130
+ self.split(/(?:[^[:alnum:]]+|(?<=[[:alnum:]])(?=[[:upper:]][[:lower:]]))/).reject{|w| w == '' }
131
+ )
187
132
  end
188
133
  end
134
+
@@ -7,8 +7,8 @@
7
7
  module ActiveFacts
8
8
  module Version
9
9
  MAJOR = 1
10
- MINOR = 0
11
- PATCH = 2
10
+ MINOR = 1
11
+ PATCH = 0
12
12
 
13
13
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
14
14
  end