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