activefacts 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -13
- data/Rakefile +2 -2
- data/bin/afgen +1 -1
- data/bin/cql +118 -27
- data/examples/CQL/Insurance.cql +2 -2
- data/examples/CQL/Metamodel.cql +3 -3
- data/examples/CQL/SchoolActivities.cql +1 -1
- data/examples/CQL/Warehousing.cql +5 -4
- data/lib/activefacts/cql.rb +1 -1
- data/lib/activefacts/cql/Language/English.treetop +2 -1
- data/lib/activefacts/cql/compiler.rb +6 -6
- data/lib/activefacts/cql/compiler/clause.rb +69 -46
- data/lib/activefacts/cql/compiler/constraint.rb +14 -14
- data/lib/activefacts/cql/compiler/entity_type.rb +24 -24
- data/lib/activefacts/cql/compiler/fact.rb +40 -27
- data/lib/activefacts/cql/compiler/fact_type.rb +16 -16
- data/lib/activefacts/cql/compiler/query.rb +12 -12
- data/lib/activefacts/cql/compiler/shared.rb +9 -0
- data/lib/activefacts/cql/compiler/value_type.rb +4 -4
- data/lib/activefacts/cql/parser.rb +9 -9
- data/lib/activefacts/generate/cql.rb +41 -20
- data/lib/activefacts/generate/helpers/oo.rb +33 -70
- data/lib/activefacts/generate/helpers/ordered.rb +61 -87
- data/lib/activefacts/generate/ruby.rb +12 -72
- data/lib/activefacts/generate/transform/surrogate.rb +13 -13
- data/lib/activefacts/input/orm.rb +72 -71
- data/lib/activefacts/persistence/columns.rb +66 -31
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +12 -12
- data/lib/activefacts/persistence/object_type.rb +15 -12
- data/lib/activefacts/persistence/reference.rb +20 -18
- data/lib/activefacts/persistence/tables.rb +40 -36
- data/lib/activefacts/support.rb +69 -123
- data/lib/activefacts/version.rb +2 -2
- data/lib/activefacts/vocabulary/extensions.rb +42 -39
- data/lib/activefacts/vocabulary/metamodel.rb +11 -1
- data/lib/activefacts/vocabulary/verbaliser.rb +28 -28
- data/spec/cql/contractions_spec.rb +1 -1
- data/spec/cql/entity_type_spec.rb +1 -1
- data/spec/cql/fact_type_matching_spec.rb +3 -3
- data/spec/cql/role_matching_spec.rb +4 -4
- data/spec/cql/samples_spec.rb +2 -2
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/helpers/array_matcher.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +2 -2
- data/spec/norma_tables_spec.rb +3 -2
- 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
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
230
|
-
|
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
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
281
|
+
trace :absorption, "Flipping references from #{object_type.name}" do
|
280
282
|
non_identifying_refs_from.each do |ref|
|
281
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
317
|
+
trace :absorption, "#{object_type.name} is fully absorbed through #{absorption_paths.inspect}"
|
316
318
|
absorption_paths.each do |ref|
|
317
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
361
|
+
if trace :absorption
|
358
362
|
undecided.each do |object_type|
|
359
|
-
|
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
|
data/lib/activefacts/support.rb
CHANGED
@@ -1,108 +1,10 @@
|
|
1
1
|
#
|
2
2
|
# ActiveFacts Support code.
|
3
|
-
# The
|
4
|
-
# Set the
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
alias_method :firstaid_initialize, :initialize
|
105
|
+
def camelcase
|
106
|
+
camelwords.join('')
|
107
|
+
end
|
179
108
|
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
+
|