activefacts 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -9,7 +9,7 @@ require 'activefacts/input/cql'
9
9
  require 'activefacts/persistence'
10
10
 
11
11
  describe "Absorption" do
12
- Prologue = %Q{
12
+ AT_Prologue = %Q{
13
13
  vocabulary Test;
14
14
  DateTime is written as DateAndTime();
15
15
  Month is written as VariableLengthText(3);
@@ -17,29 +17,29 @@ describe "Absorption" do
17
17
  PartyID is written as AutoCounter();
18
18
  ClaimID is written as AutoCounter();
19
19
  }
20
- Claim = %Q{
20
+ AT_Claim = %Q{
21
21
  Claim is identified by ClaimID where
22
22
  Claim has exactly one ClaimID,
23
23
  ClaimID is of at most one Claim;
24
24
  }
25
- Incident = %Q{
25
+ AT_Incident = %Q{
26
26
  Incident is identified by Claim where
27
27
  Claim concerns at most one Incident,
28
28
  Incident is of exactly one Claim;
29
29
  }
30
- Party = %Q{
30
+ AT_Party = %Q{
31
31
  Party is identified by PartyID where
32
32
  Party has exactly one PartyID,
33
33
  PartyID is of at most one Party;
34
34
  }
35
- Person = %Q{
35
+ AT_Person = %Q{
36
36
  Person is a kind of Party;
37
37
  }
38
38
 
39
39
  Tests = [
40
40
  { :should => "inject a value column into the table for an independent ValueType",
41
41
  :cql => %Q{
42
- #{Prologue}
42
+ #{AT_Prologue}
43
43
  Month is in exactly one Season;
44
44
  },
45
45
  :tables => { "Month" => [["Month", "Value"], ["Season"]] }
@@ -47,7 +47,7 @@ describe "Absorption" do
47
47
 
48
48
  { :should => "absorb a one-to-one along the identification path",
49
49
  :cql => %Q{
50
- #{Prologue} #{Claim} #{Incident}
50
+ #{AT_Prologue} #{AT_Claim} #{AT_Incident}
51
51
  Incident relates to loss on exactly one DateTime;
52
52
  },
53
53
  :tables => { "Claim" => [%w{Claim ID}, %w{Incident Date Time}]}
@@ -55,7 +55,7 @@ describe "Absorption" do
55
55
 
56
56
  { :should => "absorb an objectified binary with single-role UC",
57
57
  :cql => %Q{
58
- #{Prologue} #{Claim} #{Party} #{Person}
58
+ #{AT_Prologue} #{AT_Claim} #{AT_Party} #{AT_Person}
59
59
  Lodgement is where
60
60
  Claim was lodged by at most one Person;
61
61
  Lodgement was made at at most one DateTime;
@@ -0,0 +1,91 @@
1
+ #
2
+ # ActiveFacts CQL Comparison Fact Type tests
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'rspec/expectations'
7
+
8
+ require 'activefacts/support'
9
+ require 'activefacts/api/support'
10
+ require 'activefacts/cql/compiler'
11
+ require 'spec/helpers/compile_helpers'
12
+
13
+ require 'ruby-debug'; Debugger.start
14
+
15
+ describe "When matching a reading with an existing fact type" do
16
+ before :each do
17
+ extend CompileHelpers
18
+
19
+ prefix = %q{
20
+ vocabulary Tests;
21
+ Name is written as String;
22
+ year/years converts to 365.25 day;
23
+ Age is written as Integer year;
24
+ Person is identified by its Name;
25
+ Person is of Age;
26
+
27
+ // Company is identified by its Name;
28
+ // Directorship is where Person directs Company;
29
+ }
30
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
31
+ @compiler.compile(prefix)
32
+ @constellation = @compiler.vocabulary.constellation
33
+
34
+ baseline
35
+ end
36
+
37
+ describe "equality comparisons" do
38
+ before :each do
39
+ #debug_enable("binding"); debug_enable("matching"); debug_enable("matching_fails"); debug_enable("parse")
40
+ end
41
+ after :each do
42
+ #debug_disable("binding"); debug_disable("matching"); debug_disable("matching_fails"); debug_disable("parse")
43
+ end
44
+
45
+ def value_should_match value, lit, unit = nil
46
+ value.should_not be_nil
47
+ value.literal.should == lit.to_s
48
+ (!!value.is_a_string).should == lit.is_a?(String)
49
+ if unit
50
+ value.unit.should_not be_nil
51
+ value.unit.name.should == unit
52
+ else
53
+ value.unit.should be_nil
54
+ end
55
+ end
56
+
57
+ def one_join_with_value v, unit = nil
58
+ joins.size.should == 1
59
+ (jss = join_steps).size.should == 2
60
+ (jns = join_nodes).size.should == 3
61
+ integer_node = jns.detect{|jn| jn.object_type.name == 'Integer'}
62
+ integer_node.should_not be_nil
63
+ value_should_match integer_node.value, v, unit
64
+ # pending should test content of the join steps
65
+ end
66
+
67
+ it "should create a comparison fact type" do
68
+ compile %q{Person is old where Person is of Age >= 60 years; }
69
+ (new_fact_types = fact_types).size.should == 2
70
+
71
+ is_old_ft = new_fact_types.detect{|ft| ft.all_reading.detect{|r| r.text =~ /is old/} }
72
+ (is_old_ft.all_reading.map{ |r| r.expand }*', ').should == "Person is old"
73
+
74
+ comparison_ft = (new_fact_types - [is_old_ft])[0]
75
+ (comparison_ft.all_reading.map{ |r| r.expand }*', ').should == "Boolean = Age >= Integer"
76
+
77
+ one_join_with_value 60, 'year'
78
+ end
79
+
80
+ it "should create a comparison fact type twice without duplication"
81
+
82
+ it "should parse a query and comparison fact type" do
83
+ compile %q{Person is of Age >= 60 years? }
84
+ (new_fact_types = fact_types).size.should == 1
85
+ (readings = new_fact_types[0].all_reading).size.should == 1
86
+ readings.single.text.should == '{0} = {1} >= {2}'
87
+
88
+ one_join_with_value 60, 'year'
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,251 @@
1
+ #
2
+ # ActiveFacts CQL Fact Type matching tests - contractions.
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # Contractions are where a fact type clause is followed by another (with
6
+ # some conjunction except for comparisons) with one player *implicit* in
7
+ # the following clause.
8
+ #
9
+ # Right contraction elides the repetition of the right-most player,
10
+ # and left contraction elides the left-most player.
11
+ #
12
+ # So, using the notation "A rel B" for binaries,
13
+ # "A relA B relA C" for ternaries, and "C prop" for properties,
14
+ # we can write equivalences as follows.
15
+ #
16
+ # Note 1: Terms may be more than one word.
17
+ # Note 2: For any "A rel B", the example also applies to a ternary or
18
+ # higher, as appropriate.
19
+
20
+ =begin
21
+ Right contractions with 'and':
22
+ A rel B and B prop
23
+ -> A rel B who/that prop
24
+ E.g. Person pats Cat that is asleep
25
+
26
+ A rel B and B rel2 C
27
+ -> A rel B who/that rel2 C
28
+ E.g. Person pats Cat that is lying on Mat
29
+
30
+ A rel B and B rel2A C rel2A D
31
+ -> A rel B who/that rel2A C rel2B D
32
+ E.g. Person pats Cat that ate Food at Time
33
+
34
+ A rel B and B > C
35
+ -> A rel B > C
36
+ E.g. Person is of Age >= 21
37
+
38
+ A > B and B rel C
39
+ -> A > B rel C
40
+ E.g. 21 > Age of Person
41
+
42
+ Right contractions with ',':
43
+ A rel B, B rel2 C
44
+ -> A rel B who/that rel2 C
45
+
46
+ A rel B, B prop
47
+ -> A rel B who/that prop
48
+
49
+ A rel B, B > C
50
+ -> A rel B > C
51
+
52
+ A > B, B rel C
53
+ -> A > B rel C
54
+
55
+ Left contractions with 'and':
56
+ A rel B and A rel2 C
57
+ -> A rel B and rel2 C
58
+ E.g. Boy seduces Girl and is drunk
59
+
60
+ A rel B and A prop
61
+ -> A rel B and prop
62
+
63
+ A rel B and A > C
64
+ -> A rel B and > C
65
+
66
+ Left contractions with 'or':
67
+ A rel B or A rel2 C
68
+ -> A rel B or rel2 C
69
+
70
+ A rel B or A prop
71
+ -> A rel B or prop
72
+
73
+ A rel B or A > C
74
+ -> A rel B or > C
75
+
76
+ A > B or A rel C
77
+ -> A > B or rel C
78
+
79
+ Double contractions (not supported in CQL yet):
80
+ A rel B and A rel2 B
81
+ -> A rel B and rel2
82
+ E.g. Person came to Party and was invited to
83
+
84
+ Extended contractions (not supported in CQL yet) (note the ambiguity - if allowed, the first takes precedence!):
85
+ A rel B and B rel2 C and B prop
86
+ -> A rel B that rel2 C and prop
87
+ E.g. LazyDogOwner is a Person who owns Dog that barks and Dog is lazy.
88
+
89
+ A rel B and B rel2 C and A prop
90
+ -> A rel B that rel2 C and prop
91
+ E.g. LazyDogOwner is a Person who owns Dog that barks and Person is lazy.
92
+
93
+ And/or resolution:
94
+ A rel B and B rel2 C or A rel3 D
95
+ A rel B and B rel2 C or B rel3 D -> Logical, A must rel B but B can carry either rel
96
+ A rel B and B rel2 C or C rel3 D -> Illogical form (mandates B rel2 C so 'or' is meaningless)
97
+
98
+ Ambiguous, disallowed:
99
+ A > 2 + B > C
100
+
101
+ Comments from Matt on contraction and verbalisation:
102
+
103
+ I have two types of list phrases which I classify as 'header' and 'inline'.
104
+ And and or are inline, the other four (negated and/or, positive or negative
105
+ xor) all use header forms. There is also a header form of negation (it is not
106
+ true that...). The header forms (all of the following must be true: etc) can
107
+ introduce vars in the starting context, but not those introduced by previous
108
+ elements in the list.
109
+
110
+ You'll likely need something similar, though, on nested expressions. You also
111
+ have to decide where your implicit existential placement is if you use the
112
+ same var in multiple branches or under negation. Lots of fun stuff.
113
+
114
+ =end
115
+
116
+
117
+ require 'rspec/expectations'
118
+
119
+ require 'activefacts/support'
120
+ require 'activefacts/api/support'
121
+ require 'activefacts/cql/compiler'
122
+ require File.dirname(__FILE__) + '/../helpers/compile_helpers' # Can't see how to include/extend these methods correctly
123
+
124
+ describe "When compiling a join, " do
125
+ before :each do
126
+ extend CompileHelpers
127
+
128
+ prefix = %q{
129
+ vocabulary Tests;
130
+ Boy is written as String;
131
+ Girl is written as Integer;
132
+ Age is written as Integer;
133
+ Boy is of Age;
134
+ Boy is going out with Girl, Girl is going out with Boy;
135
+ }
136
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
137
+ @compiler.compile(prefix)
138
+ @constellation = @compiler.vocabulary.constellation
139
+
140
+ baseline
141
+
142
+ =begin
143
+ def self.baseline
144
+ @base_facts = @constellation.FactType.values-@constellation.ImplicitFactType.values
145
+ @base_objects = @constellation.ObjectType.values
146
+ end
147
+
148
+ def self.fact_types
149
+ @constellation.FactType.values-@base_facts-@constellation.ImplicitFactType.values
150
+ end
151
+
152
+ def self.object_types
153
+ @constellation.ObjectType.values-@base_objects
154
+ end
155
+
156
+ def self.fact_pcs fact_type
157
+ fact_type.all_role.map{|r| r.all_role_ref.map{|rr| rr.role_sequence.all_presence_constraint.to_a}}.flatten.uniq
158
+ end
159
+
160
+ def self.derivation fact_type
161
+ join = (joins = @constellation.Join.values.to_a)[0]
162
+ # PENDING: When the fact type's roles are projected, use this instead:
163
+ # joins = fact_type.all_role.map{|r| r.all_join_role.map{|jr| jr.join}}.flatten.uniq
164
+ joins.size.should == 1
165
+ joins[0]
166
+ end
167
+ =end
168
+
169
+ def self.compile string
170
+ lambda {
171
+ @compiler.compile string
172
+ }.should_not raise_error
173
+ end
174
+ end
175
+
176
+ shared_examples_for "single contractions" do
177
+ it "should produce one fact type" do
178
+ (new_fact_types = fact_types).size.should == 1
179
+ end
180
+ it "the fact type should have one reading" do
181
+ fact_type = fact_types[0]
182
+ fact_type.all_reading.size.should == 1
183
+ end
184
+ it "the fact type should have no presence constraints" do
185
+ fact_type = fact_types[0]
186
+ (pcs = fact_pcs(fact_type)).size.should == 0
187
+ end
188
+ it "should produce one join" do
189
+ fact_type = fact_types[0]
190
+ join = derivation(fact_type)
191
+ end
192
+ it "the join should have 3 nodes" do
193
+ fact_type = fact_types[0]
194
+ join = derivation(fact_type)
195
+ nodes = join.all_join_node.to_a
196
+ nodes.size.should == 3
197
+ end
198
+ it "the join should have 2 steps" do
199
+ fact_type = fact_types[0]
200
+ join = derivation(fact_type)
201
+ steps = join.all_join_step.to_a
202
+ steps.size.should == 2
203
+ end
204
+
205
+ it "and should project the fact type roles from the join" do
206
+ pending "Join roles are not yet projected" do
207
+ join = derivation(fact_type)
208
+ joins = fact_type.all_role.map{|r| r.all_join_role.map{|jr| jr.join}}.flatten.uniq
209
+ joins.size == 1
210
+ joins.should == [join]
211
+ end
212
+ end
213
+ end
214
+
215
+ describe "right contractions having" do
216
+ describe "a single contraction using 'who'" do
217
+ before :each do
218
+ compile %q{Boy is relevant where Girl is going out with Boy who is of Age; }
219
+ end
220
+
221
+ it_should_behave_like "single contractions"
222
+ end
223
+
224
+ describe "a single contraction using 'that'" do
225
+ before :each do
226
+ compile %q{Boy is relevant where Girl is going out with Boy that is of Age; }
227
+ end
228
+
229
+ it_should_behave_like "single contractions"
230
+ end
231
+ end
232
+
233
+ describe "left contractions having" do
234
+ describe "a single contraction" do
235
+ before :each do
236
+ compile %q{Boy is relevant where Boy is of Age and is going out with Girl; }
237
+ end
238
+
239
+ it_should_behave_like "single contractions"
240
+ end
241
+ end
242
+ end
243
+
244
+ =begin
245
+ Examples on the Blog model:
246
+
247
+ Post is nice where Post was written by Author and belongs to Topic and includes Ordinal paragraph;
248
+ Post is nice where Post was written by Author and belongs to Topic or Post includes Ordinal paragraph or has Post Id;
249
+ Post is nice where Post was written by Author and belongs to Topic or includes Ordinal paragraph;
250
+ Post is nice where Post was written by Author and belongs to Topic or Post includes Ordinal paragraph or has Post Id;
251
+ =end
@@ -0,0 +1,319 @@
1
+ #
2
+ # ActiveFacts CQL Fact Type matching tests
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'rspec/expectations'
7
+
8
+ require 'activefacts/support'
9
+ require 'activefacts/api/support'
10
+ require 'activefacts/cql/compiler'
11
+ # require File.dirname(__FILE__) + '/../helpers/compiler_helper' # Can't see how to include/extend these methods correctly
12
+
13
+ describe "When compiling an entity type, " do
14
+ MatchingPrefix = %q{
15
+ vocabulary Tests;
16
+ Boy is written as String;
17
+ Girl is written as Integer;
18
+ }
19
+ BaseObjectTypes = 4 # Integer, String, Boy, Girl
20
+
21
+ def self.SingleFact &b
22
+ lambda {|c|
23
+ real_fact_types = c.FactType.values-c.ImplicitFactType.values
24
+ real_fact_types.size.should == 1
25
+ @fact_type = real_fact_types[0]
26
+ b.call(@fact_type) if b
27
+ @fact_type
28
+ }
29
+ end
30
+
31
+ def self.FactHavingPlayers(*a, &b)
32
+ lambda {|c|
33
+ @fact_type = c.FactType.detect do |key, ft|
34
+ ft.all_role.map{|r| r.object_type.name}.sort == a.sort
35
+ end
36
+ b.call(@fact_type) if b
37
+ @fact_type
38
+ }
39
+ end
40
+
41
+ def self.PresenceConstraints fact_type, &b
42
+ @presence_constraints =
43
+ fact_type.all_role.map{|r|
44
+ r.all_role_ref.map{|rr|
45
+ rr.role_sequence.all_presence_constraint.to_a
46
+ }
47
+ }.flatten.uniq
48
+ b.call(@presence_constraints) if b
49
+ @presence_constraints
50
+ end
51
+
52
+ def self.Readings fact_type, &b
53
+ @readings = fact_type.all_reading.sort_by{|r| r.ordinal}
54
+ b.call(@readings) if b
55
+ @readings
56
+ end
57
+
58
+ def self.ReadingCount n
59
+ lambda {|c|
60
+ unless @fact_type.all_reading.size == n
61
+ puts "SPEC FAILED, wrong number of readings (should be #{n}):\n\t#{
62
+ @fact_type.all_reading.map{ |r| r.expand}*"\n\t"
63
+ }"
64
+ end
65
+ @fact_type.all_reading.size.should == n
66
+ }
67
+ end
68
+
69
+ def self.PresenceConstraintCount n
70
+ lambda{ |c|
71
+ @fact_type.all_role.map{|r|
72
+ r.all_role_ref.map{|rr|
73
+ rr.role_sequence.all_presence_constraint.to_a
74
+ }
75
+ }.flatten.uniq.size.should == n
76
+ }
77
+ end
78
+
79
+ def self.ObjectTypeCount n
80
+ lambda {|c|
81
+ @constellation = c
82
+ c.ObjectType.values.size.should == n
83
+ }
84
+ end
85
+
86
+ def self.ObjectType name, &b
87
+ lambda {|c|
88
+ @object_type = c.ObjectType[[["Tests"], name]]
89
+ @object_type.should_not == nil
90
+ b.call(@object_type) if b
91
+ @object_type
92
+ }
93
+ end
94
+
95
+ def self.WrittenAs name
96
+ lambda {|c|
97
+ @base_type = c.ObjectType[[["Tests"], name]]
98
+ @base_type.class.should == ActiveFacts::Metamodel::ValueType
99
+ @object_type.class.should == ActiveFacts::Metamodel::ValueType
100
+ @object_type.supertype.should == @base_type
101
+ }
102
+ end
103
+
104
+ def self.PreferredIdentifier num_roles
105
+ lambda {|c|
106
+ @preferred_identifier = @object_type.preferred_identifier
107
+ @preferred_identifier.should_not == nil
108
+ @preferred_identifier.role_sequence.all_role_ref.size.should == num_roles
109
+ #@preferred_identifier.min_frequency.should == 1
110
+ @preferred_identifier.max_frequency.should == 1
111
+ @preferred_identifier.is_preferred_identifier.should == true
112
+ }
113
+ end
114
+
115
+ def self.PreferredIdentifierRolePlayedBy name, num = 0
116
+ lambda {|c|
117
+ @preferred_identifier.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[num].role.object_type.name.should == name
118
+ }
119
+ end
120
+
121
+ class BlackHole
122
+ def method_missing(m,*a,&b)
123
+ self
124
+ end
125
+ end
126
+ class PendingSilencer
127
+ def STDOUT; BlackHole.new; end
128
+ def puts; BlackHole.new; end
129
+ def p; BlackHole.new; end
130
+ end
131
+
132
+ def self.pending(msg = "TODO", &b)
133
+ lambda {|*c|
134
+ raised = nil
135
+ begin
136
+ example = b.call
137
+ eval(lambda { example.call(*c) }, BlackHole.new)
138
+ rescue => raised
139
+ end
140
+ raise RSpec::Core::PendingExampleFixedError.new(msg) unless raised
141
+ throw :pending_declared_in_example, msg
142
+ }
143
+ end
144
+
145
+ def self.ReadingContainsHyphenatedWord reading_num
146
+ lambda {|c|
147
+ hyphenated_reading =
148
+ c.FactType.values[0].all_reading.select {|reading|
149
+ reading.ordinal == reading_num
150
+ }[0]
151
+ hyphenated_reading.should_not == nil
152
+ (hyphenated_reading.text =~ /[a-z]-[a-z]/).should_not == nil
153
+ }
154
+ end
155
+
156
+ EntityIdentificationTests = [
157
+ [
158
+ # REVISIT: At present, this doesn't add the minimum frequency constraint that a preferred identifier requires.
159
+ %q{Thong is written as String;},
160
+ %q{Thing is identified by Thong where Thing has one Thong;},
161
+ SingleFact() do |fact_type|
162
+ Readings(fact_type).size.should == 1
163
+ PresenceConstraints(fact_type).size.should == 2
164
+ end,
165
+ ObjectType('Thong') do |object_type|
166
+ object_type.class.should == ActiveFacts::Metamodel::ValueType
167
+ # REVISIT: Figure out how WrittenAs can access the constellation.
168
+ WrittenAs('String')
169
+ end,
170
+ ObjectTypeCount(2+BaseObjectTypes),
171
+ ObjectType('Thing'),
172
+ PreferredIdentifier(1),
173
+ PreferredIdentifierRolePlayedBy('Thong'),
174
+ ],
175
+
176
+ [ # Auto-create Id and Thing Id:
177
+ %q{Thing is identified by its Id;},
178
+ SingleFact() do |fact_type|
179
+ Readings(fact_type).size.should == 2
180
+ PresenceConstraints(fact_type).size.should == 2
181
+ end,
182
+ ObjectTypeCount(3+BaseObjectTypes),
183
+ ObjectType('Thing'),
184
+ PreferredIdentifier(1),
185
+ PreferredIdentifierRolePlayedBy('Thing Id'),
186
+ ],
187
+
188
+ [ # Auto-create Thing Id:
189
+ %q{Id is written as String;},
190
+ %q{Thing is identified by its Id;},
191
+ SingleFact() do |fact_type|
192
+ Readings(fact_type).size.should == 2
193
+ PresenceConstraints(fact_type).size.should == 2
194
+ end,
195
+ ObjectTypeCount(3+BaseObjectTypes),
196
+ ObjectType('Thing'),
197
+ PreferredIdentifier(1),
198
+ PreferredIdentifierRolePlayedBy('Thing Id'),
199
+ ],
200
+
201
+ [ # Auto-create nothing (identifying value type exists already)
202
+ %q{Thing Id is written as String;},
203
+ %q{Thing is identified by its Id;},
204
+ SingleFact() do |fact_type|
205
+ Readings(fact_type).size.should == 2
206
+ PresenceConstraints(fact_type).size.should == 2
207
+ end,
208
+ ObjectTypeCount(2+BaseObjectTypes),
209
+ ObjectType('Thing'),
210
+ PreferredIdentifier(1),
211
+ PreferredIdentifierRolePlayedBy('Thing Id'),
212
+ ],
213
+
214
+ [ # Auto-create nothing (identifying entity type exists already so don't create a VT)
215
+ %q{Id is written as Id;},
216
+ %q{Thing Id is identified by Id where Thing Id has one Id, Id is of one Thing Id;},
217
+ %q{Thing is identified by its Id;},
218
+ FactHavingPlayers("Thing", "Thing Id") do |fact_type|
219
+ Readings(fact_type).size.should == 2
220
+ PresenceConstraints(fact_type).size.should == 2
221
+ end,
222
+ ObjectTypeCount(3+BaseObjectTypes),
223
+ ObjectType('Thing'),
224
+ PreferredIdentifier(1),
225
+ PreferredIdentifierRolePlayedBy('Thing Id'),
226
+ ],
227
+
228
+ [
229
+ %q{Thong is written as String;},
230
+ %q{Thing is identified by Thong where Thing has one Thong, Thong is of one Thing;},
231
+ SingleFact() do |fact_type|
232
+ Readings(fact_type).size.should == 2
233
+ PresenceConstraints(fact_type).size.should == 2
234
+ end,
235
+ ObjectTypeCount(2+BaseObjectTypes),
236
+ ObjectType('Thing'),
237
+ PreferredIdentifier(1),
238
+ PreferredIdentifierRolePlayedBy('Thong'),
239
+ ],
240
+
241
+ [ # Objectified fact type with internal identification
242
+ %q{Relationship is where Boy relates to Girl;},
243
+ SingleFact() do |fact_type|
244
+ Readings(fact_type).size.should == 1
245
+ PresenceConstraints(fact_type).size.should == 1
246
+ end,
247
+ ObjectTypeCount(1+BaseObjectTypes),
248
+ ObjectType('Relationship'),
249
+ PreferredIdentifier(2),
250
+ # PreferredIdentifierRolePlayedBy('Thong'),
251
+ ],
252
+
253
+ [ # Objectified fact type with external identification
254
+ %q{Relationship is identified by its Id where Boy relates to Girl;},
255
+ ObjectTypeCount(3+BaseObjectTypes),
256
+ ObjectType('Relationship'),
257
+ PreferredIdentifier(1), # 1 role in PI
258
+ PreferredIdentifierRolePlayedBy('Relationship Id'),
259
+ FactHavingPlayers('Relationship', 'Relationship Id') do |fact_type|
260
+ Readings(fact_type).size.should == 2
261
+ PresenceConstraints(fact_type).size.should == 2
262
+ fact_type.all_reading.detect{|r| r.text == '{0} has {1}'}.should_not == nil
263
+ fact_type.all_reading.detect{|r| r.text == '{0} is of {1}'}.should_not == nil
264
+ end,
265
+ FactHavingPlayers('Boy', 'Girl') do |fact_type|
266
+ fact_type.entity_type.should == @object_type
267
+ end,
268
+ ],
269
+
270
+ [ # Objectified fact type with external identification and explicit reading
271
+ %q{Relationship is identified by its Id where Boy relates to Girl, Relationship is known by Relationship Id;},
272
+ ObjectTypeCount(3+BaseObjectTypes),
273
+ ObjectType('Relationship'),
274
+ PreferredIdentifier(1),
275
+ PreferredIdentifierRolePlayedBy('Relationship Id'),
276
+ FactHavingPlayers('Relationship', 'Relationship Id') do |fact_type|
277
+ Readings(fact_type).size.should == 2
278
+ PresenceConstraints(fact_type).size.should == 2
279
+ fact_type.all_reading.detect{|r| r.text == '{0} is known by {1}'}.should_not == nil
280
+ fact_type.all_reading.detect{|r| r.text == '{0} is of {1}'}.should_not == nil
281
+ end,
282
+ FactHavingPlayers('Boy', 'Girl') do |fact_type|
283
+ fact_type.entity_type.should == @object_type
284
+ end,
285
+ ],
286
+
287
+ ]
288
+
289
+ AllTests =
290
+ EntityIdentificationTests
291
+
292
+ before :each do
293
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
294
+ end
295
+
296
+ AllTests.each do |tests|
297
+ it "should process '#{(tests.select{|t| t.is_a?(String)}*' ').gsub(/\s+/m,' ')}' correctly" do
298
+ tests.each do |test|
299
+ case test
300
+ when String
301
+ result = @compiler.compile(MatchingPrefix+test)
302
+ puts @compiler.failure_reason unless result
303
+ result.should_not be_nil
304
+ when Proc
305
+ begin
306
+ test.call(@compiler.vocabulary.constellation)
307
+ rescue RSpec::Expectations::ExpectationNotMetError
308
+ raise
309
+ rescue RSpec::Core::ExamplePendingError
310
+ raise
311
+ rescue => e
312
+ puts "Failed on\n\t"+tests.select{|t| t.is_a?(String)}*" "
313
+ raise
314
+ end
315
+ end
316
+ end
317
+ end
318
+ end
319
+ end