activefacts 0.8.9 → 0.8.10

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.
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