activefacts 0.8.9 → 0.8.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Manifest.txt +28 -33
- data/Rakefile +11 -12
- data/bin/cql +90 -46
- data/examples/CQL/Blog.cql +2 -1
- data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
- data/examples/CQL/Death.cql +1 -1
- data/examples/CQL/Diplomacy.cql +9 -9
- data/examples/CQL/Genealogy.cql +3 -2
- data/examples/CQL/Insurance.cql +10 -7
- data/examples/CQL/JoinEquality.cql +2 -2
- data/examples/CQL/Marriage.cql +1 -1
- data/examples/CQL/Metamodel.cql +73 -53
- data/examples/CQL/MetamodelNext.cql +89 -67
- data/examples/CQL/OneToOnes.cql +2 -2
- data/examples/CQL/ServiceDirector.cql +10 -5
- data/examples/CQL/Supervision.cql +3 -3
- data/examples/CQL/Tests.Test5.Load.cql +1 -1
- data/examples/CQL/Warehousing.cql +4 -2
- data/lib/activefacts/cql/CQLParser.treetop +26 -60
- data/lib/activefacts/cql/Context.treetop +12 -2
- data/lib/activefacts/cql/Expressions.treetop +14 -30
- data/lib/activefacts/cql/FactTypes.treetop +165 -110
- data/lib/activefacts/cql/Language/English.treetop +167 -54
- data/lib/activefacts/cql/LexicalRules.treetop +16 -2
- data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
- data/lib/activefacts/cql/Terms.treetop +57 -27
- data/lib/activefacts/cql/ValueTypes.treetop +39 -13
- data/lib/activefacts/cql/compiler.rb +5 -3
- data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
- data/lib/activefacts/cql/compiler/constraint.rb +178 -275
- data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
- data/lib/activefacts/cql/compiler/expression.rb +418 -0
- data/lib/activefacts/cql/compiler/fact.rb +146 -145
- data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
- data/lib/activefacts/cql/compiler/join.rb +159 -0
- data/lib/activefacts/cql/compiler/shared.rb +51 -23
- data/lib/activefacts/cql/compiler/value_type.rb +56 -2
- data/lib/activefacts/cql/parser.rb +15 -4
- data/lib/activefacts/generate/absorption.rb +7 -7
- data/lib/activefacts/generate/cql.rb +100 -37
- data/lib/activefacts/generate/oo.rb +28 -51
- data/lib/activefacts/generate/ordered.rb +60 -36
- data/lib/activefacts/generate/ruby.rb +6 -6
- data/lib/activefacts/generate/sql/server.rb +4 -4
- data/lib/activefacts/input/orm.rb +71 -53
- data/lib/activefacts/persistence.rb +1 -1
- data/lib/activefacts/persistence/columns.rb +27 -23
- data/lib/activefacts/persistence/foreignkey.rb +6 -6
- data/lib/activefacts/persistence/index.rb +17 -17
- data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
- data/lib/activefacts/persistence/reference.rb +61 -36
- data/lib/activefacts/persistence/tables.rb +61 -59
- data/lib/activefacts/support.rb +54 -29
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +99 -54
- data/lib/activefacts/vocabulary/metamodel.rb +43 -37
- data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
- data/spec/absorption_spec.rb +8 -8
- data/spec/cql/comparison_spec.rb +91 -0
- data/spec/cql/contractions_spec.rb +251 -0
- data/spec/cql/entity_type_spec.rb +319 -0
- data/spec/cql/expressions_spec.rb +63 -0
- data/spec/cql/fact_type_matching_spec.rb +283 -0
- data/spec/cql/french_spec.rb +21 -0
- data/spec/cql/parser/bad_literals_spec.rb +86 -0
- data/spec/cql/parser/constraints_spec.rb +19 -0
- data/spec/cql/parser/entity_types_spec.rb +106 -0
- data/spec/cql/parser/expressions_spec.rb +179 -0
- data/spec/cql/parser/fact_types_spec.rb +41 -0
- data/spec/cql/parser/literals_spec.rb +312 -0
- data/spec/cql/parser/pragmas_spec.rb +89 -0
- data/spec/cql/parser/value_types_spec.rb +42 -0
- data/spec/cql/role_matching_spec.rb +147 -0
- data/spec/cql/samples_spec.rb +9 -9
- data/spec/cql_cql_spec.rb +1 -1
- data/spec/cql_dm_spec.rb +116 -0
- data/spec/cql_mysql_spec.rb +1 -1
- data/spec/cql_ruby_spec.rb +1 -1
- data/spec/cql_sql_spec.rb +3 -3
- data/spec/cql_symbol_tables_spec.rb +30 -30
- data/spec/cqldump_spec.rb +4 -4
- data/spec/helpers/array_matcher.rb +32 -27
- data/spec/helpers/diff_matcher.rb +6 -26
- data/spec/helpers/file_matcher.rb +41 -32
- data/spec/helpers/parse_to_ast_matcher.rb +76 -0
- data/spec/helpers/string_matcher.rb +32 -31
- data/spec/norma_cql_spec.rb +1 -1
- data/spec/norma_ruby_spec.rb +1 -1
- data/spec/norma_ruby_sql_spec.rb +1 -1
- data/spec/norma_sql_spec.rb +3 -1
- data/spec/norma_tables_spec.rb +1 -1
- data/spec/ruby_api_spec.rb +23 -0
- data/spec/spec_helper.rb +5 -4
- metadata +66 -66
- data/examples/CQL/OrienteeringER.cql +0 -58
- data/lib/activefacts/api.rb +0 -44
- data/lib/activefacts/api/concept.rb +0 -410
- data/lib/activefacts/api/constellation.rb +0 -128
- data/lib/activefacts/api/entity.rb +0 -256
- data/lib/activefacts/api/instance.rb +0 -60
- data/lib/activefacts/api/instance_index.rb +0 -80
- data/lib/activefacts/api/numeric.rb +0 -167
- data/lib/activefacts/api/role.rb +0 -80
- data/lib/activefacts/api/role_proxy.rb +0 -70
- data/lib/activefacts/api/role_values.rb +0 -117
- data/lib/activefacts/api/standard_types.rb +0 -87
- data/lib/activefacts/api/support.rb +0 -65
- data/lib/activefacts/api/value.rb +0 -135
- data/lib/activefacts/api/vocabulary.rb +0 -82
- data/spec/api/autocounter.rb +0 -82
- data/spec/api/constellation.rb +0 -130
- data/spec/api/entity_type.rb +0 -103
- data/spec/api/instance.rb +0 -461
- data/spec/api/roles.rb +0 -124
- data/spec/api/value_type.rb +0 -112
- data/spec/api_spec.rb +0 -13
- data/spec/cql/matching_spec.rb +0 -517
- data/spec/cql/unit_spec.rb +0 -394
- data/spec/spec.opts +0 -1
data/spec/absorption_spec.rb
CHANGED
@@ -9,7 +9,7 @@ require 'activefacts/input/cql'
|
|
9
9
|
require 'activefacts/persistence'
|
10
10
|
|
11
11
|
describe "Absorption" do
|
12
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#{
|
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
|
-
#{
|
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
|
-
#{
|
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
|