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
@@ -0,0 +1,89 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts tests: Test the CQL parser by looking at its parse trees.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'activefacts/cql'
|
7
|
+
require 'activefacts/support'
|
8
|
+
require 'activefacts/api/support'
|
9
|
+
require 'helpers/test_parser'
|
10
|
+
|
11
|
+
describe "Entity Types" do
|
12
|
+
IndependentObjectTypes = [
|
13
|
+
# Value types
|
14
|
+
[ "a is written as b [independent];",
|
15
|
+
['ValueType: a is written as b, pragmas [independent];'],
|
16
|
+
],
|
17
|
+
[ "a [independent] is written as b;",
|
18
|
+
['ValueType: a is written as b, pragmas [independent];'],
|
19
|
+
],
|
20
|
+
|
21
|
+
# Entity types
|
22
|
+
[ "a is identified by its id [independent];",
|
23
|
+
['EntityType: a identified by its id, pragmas [independent];']
|
24
|
+
],
|
25
|
+
[ "a [independent] is identified by its id;",
|
26
|
+
['EntityType: a identified by its id, pragmas [independent];']
|
27
|
+
],
|
28
|
+
[ "a is independent identified by its id;",
|
29
|
+
['EntityType: a identified by its id, pragmas [independent];']
|
30
|
+
],
|
31
|
+
[ "a is identified by b [independent] where a has one b;",
|
32
|
+
['EntityType: a [{b}] where [{a} "has" {[1..1] b}], pragmas [independent];']
|
33
|
+
],
|
34
|
+
[ "a is independent identified by b where a has one b;",
|
35
|
+
['EntityType: a [{b}] where [{a} "has" {[1..1] b}], pragmas [independent];']
|
36
|
+
],
|
37
|
+
|
38
|
+
# Subtypes
|
39
|
+
[ "Employee [independent] is a kind of Person;",
|
40
|
+
['EntityType: Employee < Person nil, pragmas [independent];']
|
41
|
+
],
|
42
|
+
[ "Employee is a kind of Person [independent];",
|
43
|
+
['EntityType: Employee < Person nil, pragmas [independent];']
|
44
|
+
],
|
45
|
+
[ "Employee is a kind of independent Person;",
|
46
|
+
['EntityType: Employee < Person nil, pragmas [independent];']
|
47
|
+
],
|
48
|
+
[ "Employee is a kind of Person identified by its id [independent];",
|
49
|
+
["EntityType: Employee < Person identified by its id, pragmas [independent];"]
|
50
|
+
],
|
51
|
+
|
52
|
+
# Fact Types
|
53
|
+
[ "Director is where c relates to b;",
|
54
|
+
["FactType: Director [{c} \"relates to\" {b}]"]
|
55
|
+
],
|
56
|
+
[ "Director [independent] is where c relates to b;",
|
57
|
+
["FactType: Director [{c} \"relates to\" {b}], pragmas [independent]"]
|
58
|
+
],
|
59
|
+
[ "Director is independent where c relates to b;",
|
60
|
+
["FactType: Director [{c} \"relates to\" {b}], pragmas [independent]"]
|
61
|
+
],
|
62
|
+
]
|
63
|
+
|
64
|
+
PragmaObjectTypes =
|
65
|
+
IndependentObjectTypes
|
66
|
+
|
67
|
+
before :each do
|
68
|
+
@parser = TestParser.new
|
69
|
+
@parser.parse_all("c is written as b;", :definition)
|
70
|
+
end
|
71
|
+
|
72
|
+
PragmaObjectTypes.each do |c|
|
73
|
+
source, ast = *c
|
74
|
+
it "should parse #{source.inspect}" do
|
75
|
+
result = @parser.parse_all(source, :definition)
|
76
|
+
|
77
|
+
puts @parser.failure_reason unless result
|
78
|
+
result.should_not be_nil
|
79
|
+
|
80
|
+
canonical_form = result.map{|d| d.ast.to_s}
|
81
|
+
if ast
|
82
|
+
canonical_form.should == ast
|
83
|
+
else
|
84
|
+
pending "#{source.inspect} should compile to\n" +
|
85
|
+
"\t#{canonical_form}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts tests: Test the CQL parser by looking at its parse trees.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'activefacts/cql'
|
7
|
+
require 'activefacts/support'
|
8
|
+
require 'activefacts/api/support'
|
9
|
+
require 'helpers/test_parser'
|
10
|
+
|
11
|
+
describe "Value Types" do
|
12
|
+
ValueTypes = [
|
13
|
+
[ "a is written as b(1, 2) inch restricted to { 3 .. 4 } inch ;",
|
14
|
+
['ValueType: a is written as b(1, 2) in [["inch", 1]] ValueConstraint to ([3..4]) in [["inch", 1]];']
|
15
|
+
],
|
16
|
+
# [ "a c is written as b(1, 2) inch restricted to { 3 .. 4 } inch ;",
|
17
|
+
# [["a c", [:value_type, "b", [1, 2], "inch", [[3, 4]]]]]
|
18
|
+
# ],
|
19
|
+
]
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
@parser = TestParser.new
|
23
|
+
end
|
24
|
+
|
25
|
+
ValueTypes.each do |c|
|
26
|
+
source, ast = *c
|
27
|
+
it "should parse #{source.inspect}" do
|
28
|
+
result = @parser.parse_all(source, :definition)
|
29
|
+
|
30
|
+
puts @parser.failure_reason unless result
|
31
|
+
result.should_not be_nil
|
32
|
+
|
33
|
+
canonical_form = result.map{|d| d.ast.to_s}
|
34
|
+
if ast
|
35
|
+
canonical_form.should == ast
|
36
|
+
else
|
37
|
+
puts "#{source.inspect} should compile to"
|
38
|
+
puts "\t#{canonical_form}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,147 @@
|
|
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 'spec/helpers/compile_helpers'
|
12
|
+
|
13
|
+
describe "When comparing roles of a reading with an existing reading" do
|
14
|
+
before :each do
|
15
|
+
extend CompileHelpers
|
16
|
+
|
17
|
+
prefix = %q{
|
18
|
+
vocabulary Tests;
|
19
|
+
Boy is written as String;
|
20
|
+
Girl is written as Integer;
|
21
|
+
}
|
22
|
+
@compiler = ActiveFacts::CQL::Compiler.new('Test')
|
23
|
+
@compiler.compile(prefix)
|
24
|
+
@constellation = @compiler.vocabulary.constellation
|
25
|
+
|
26
|
+
baseline
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "producing correct side effects" do
|
30
|
+
before :each do
|
31
|
+
@compiler.compile %q{ Boy is going out with Girl; }
|
32
|
+
@simple_ft = fact_types[0]
|
33
|
+
baseline
|
34
|
+
@compiler.compile %q{ Boy dislikes ugly-Girl; }
|
35
|
+
@ugly_ft = fact_types[0]
|
36
|
+
baseline
|
37
|
+
@compiler.compile %q{ Boy -monster hurts Girl; }
|
38
|
+
@hurts_ft = fact_types[0]
|
39
|
+
baseline
|
40
|
+
#debug_enable("matching"); debug_enable("matching_fails"); debug_enable("parse")
|
41
|
+
end
|
42
|
+
after :each do
|
43
|
+
# debug_disable("matching"); debug_disable("matching_fails"); debug_disable("parse")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should match exact reading" do
|
47
|
+
parse %q{Boy is going out with Girl;}
|
48
|
+
@asts.size.should == 1
|
49
|
+
side_effects = match_readings_to_existing(@asts[0], @simple_ft.all_reading.single)
|
50
|
+
side_effects[0].should_not be_nil
|
51
|
+
side_effects.to_s.should == 'side-effects are [{Boy} absorbs 0/0 at 0, {Girl} absorbs 0/0 at 5]'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should match with explicit leading adjective" do
|
55
|
+
parse %q{Boy dislikes ugly-Girl;}
|
56
|
+
@asts.size.should == 1
|
57
|
+
side_effects = match_readings_to_existing(@asts[0], @ugly_ft.all_reading.single)
|
58
|
+
side_effects[0].should_not be_nil
|
59
|
+
side_effects.to_s.should == 'side-effects are [{Boy} absorbs 0/0 at 0, {ugly- Girl} absorbs 0/0 at 2]'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should match with implicit leading adjective" do
|
63
|
+
parse %q{Boy dislikes ugly Girl;}
|
64
|
+
@asts.size.should == 1
|
65
|
+
side_effects = match_readings_to_existing(@asts[0], @ugly_ft.all_reading.single)
|
66
|
+
side_effects[0].should_not be_nil
|
67
|
+
side_effects.to_s.should == 'side-effects are [{Boy} absorbs 0/0 at 0, {Girl} absorbs 1/0 at 3]'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should match with local leading adjective" do
|
71
|
+
parse %q{
|
72
|
+
bad-Boy is going out with Girl,
|
73
|
+
Girl likes bad-Boy; // New reading, no match expected, but must use residual adjective
|
74
|
+
}
|
75
|
+
@asts.size.should == 1
|
76
|
+
side_effects = match_readings_to_existing(@asts[0], @simple_ft.all_reading.single)
|
77
|
+
side_effects.size.should == 2
|
78
|
+
side_effects[0].should_not be_nil
|
79
|
+
side_effects[1].should be_nil
|
80
|
+
pending side_effects[0].to_s
|
81
|
+
side_effects[0].to_s.should == '[{bad- Boy} absorbs 0/0 at 0 with residual adjectives, {Girl} absorbs 0/0 at 5] with residual adjectives'
|
82
|
+
#side_effects[1].to_s.should == ''
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should match with explicit and local leading adjective" do
|
86
|
+
parse %q{
|
87
|
+
Boy dislikes nasty- ugly Girl,
|
88
|
+
nasty- Girl likes Boy; // New reading, no match expected, but must use residual adjective
|
89
|
+
}
|
90
|
+
@asts.size.should == 1
|
91
|
+
side_effects = match_readings_to_existing(@asts[0], @ugly_ft.all_reading.single)
|
92
|
+
side_effects.size.should == 2
|
93
|
+
pending "Matched adjectives must be removed and the role rebound before deciding whether residual adjectives have a purpose" do
|
94
|
+
side_effects[0].should_not be_nil
|
95
|
+
puts side_effects[0].to_s
|
96
|
+
#side_effects.to_s.should == ''
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Trailing adjectives
|
101
|
+
it "should match with explicit trailing adjective" do
|
102
|
+
parse %q{Boy-monster hurts Girl;}
|
103
|
+
@asts.size.should == 1
|
104
|
+
side_effects = match_readings_to_existing(@asts[0], @hurts_ft.all_reading.single)
|
105
|
+
pending "Thinks trailing adjectives are always residual"
|
106
|
+
side_effects[0].should_not be_nil
|
107
|
+
side_effects.to_s.should == ''
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should match with implicit trailing adjective" do
|
111
|
+
parse %q{Boy monster hurts Girl;}
|
112
|
+
@asts.size.should == 1
|
113
|
+
side_effects = match_readings_to_existing(@asts[0], @hurts_ft.all_reading.single)
|
114
|
+
side_effects[0].should_not be_nil
|
115
|
+
side_effects.to_s.should == 'side-effects are [{Boy} absorbs 0/1 at 0, {Girl} absorbs 0/0 at 3]'
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should match with local trailing adjective" do
|
119
|
+
parse %q{
|
120
|
+
Boy is going out with Girl-troublemaker,
|
121
|
+
Girl troublemaker likes Boy;
|
122
|
+
}
|
123
|
+
@asts.size.should == 1
|
124
|
+
side_effects = match_readings_to_existing(@asts[0], @simple_ft.all_reading.single)
|
125
|
+
side_effects.size.should == 2
|
126
|
+
side_effects[0].should_not be_nil
|
127
|
+
side_effects.to_s.should == 'side-effects are [{Boy} absorbs 0/0 at 0, {Girl -troublemaker} absorbs 0/0 at 5 with residual adjectives] with residual adjectives'
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should match with explicit and local trailing adjective" do
|
131
|
+
parse %q{
|
132
|
+
Boy monster -foo hurts Girl,
|
133
|
+
Girl likes Boy foo;
|
134
|
+
}
|
135
|
+
@asts.size.should == 1
|
136
|
+
side_effects = match_readings_to_existing(@asts[0], @hurts_ft.all_reading.single)
|
137
|
+
side_effects.size.should == 2
|
138
|
+
|
139
|
+
pending "Matched adjectives must be removed and the role rebound before deciding whether residual adjectives have a purpose" do
|
140
|
+
side_effects[0].should_not be_nil
|
141
|
+
puts side_effects.to_s
|
142
|
+
# side_effects.to_s.should == ''
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
data/spec/cql/samples_spec.rb
CHANGED
@@ -118,29 +118,29 @@ describe "Sample data" do
|
|
118
118
|
# REVISIT: Include the instance_names of all role players
|
119
119
|
end
|
120
120
|
|
121
|
-
if i.
|
122
|
-
return "#{i.
|
121
|
+
if i.object_type.is_a?(ActiveFacts::Metamodel::ValueType)
|
122
|
+
return "#{i.object_type.name} #{i.value}"
|
123
123
|
end
|
124
124
|
|
125
|
-
if i.
|
126
|
-
return "#{i.
|
125
|
+
if i.object_type.fact_type # An instance of an objectified fact type
|
126
|
+
return "#{i.object_type.name} where #{instance_name(i.fact)}"
|
127
127
|
end
|
128
128
|
|
129
129
|
# It's an entity that's not an objectified fact type
|
130
130
|
# REVISIT: If it has a simple identifier, there's no need to fully verbalise the identifying facts
|
131
|
-
pi = i.
|
131
|
+
pi = i.object_type.preferred_identifier
|
132
132
|
identifying_role_refs = pi.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
133
|
-
return "#{i.
|
133
|
+
return "#{i.object_type.name}" +
|
134
134
|
" is identified by " +
|
135
135
|
identifying_role_refs.map do |rr|
|
136
136
|
[ (l = rr.leading_adjective) ? l+"-" : nil,
|
137
|
-
rr.role.role_name || rr.role.
|
137
|
+
rr.role.role_name || rr.role.object_type.name,
|
138
138
|
(t = rr.trailing_adjective) ? l+"-" : nil
|
139
139
|
].compact*""
|
140
140
|
end * " and " +
|
141
141
|
" where " +
|
142
142
|
identifying_role_refs.map do |rr| # Go through the identifying roles and emit the facts that define them
|
143
|
-
instance_role = i.
|
143
|
+
instance_role = i.object_type.all_role.detect{|r| r.fact_type == rr.role.fact_type}
|
144
144
|
identifying_fact = i.all_role_value.detect{|rv| rv.fact.fact_type == rr.role.fact_type}.fact
|
145
145
|
#counterpart_role = (rr.role.fact_type.all_role.to_a-[instance_role])[0]
|
146
146
|
#identifying_instance = counterpart_role.all_role_value.detect{|rv| rv.fact == identifying_fact}.instance
|
@@ -223,7 +223,7 @@ describe "Sample data" do
|
|
223
223
|
end.should raise_error
|
224
224
|
|
225
225
|
if missing
|
226
|
-
missing.each do |m|
|
226
|
+
Array(missing).each do |m|
|
227
227
|
@exception.message.should =~ (m.is_a?(Regexp) ? m : Regexp.new(Regexp.escape(m)))
|
228
228
|
end
|
229
229
|
else
|
data/spec/cql_cql_spec.rb
CHANGED
data/spec/cql_dm_spec.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts tests: Parse all CQL files and check the generated DataMapper models
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
# From IRC, on how to extract the SQL that DM creates for a set of models:
|
6
|
+
#
|
7
|
+
# I can tell you that ::DataObjects::Connection#log gets you the SQL
|
8
|
+
# after its run (not exactly what you asked for, but a place to start
|
9
|
+
# looking).
|
10
|
+
#
|
11
|
+
# your choices are intercepting
|
12
|
+
# ::DataMapper::Adapters::DataObjectsAdapter#{select,execute} for
|
13
|
+
# direct SQL calls, or at a lower level,
|
14
|
+
# ::DataObjects::Connection#create_command for all SQL.
|
15
|
+
#
|
16
|
+
# one trick I've been using is to selectively intercept the latter
|
17
|
+
# and not let it run (no-op it but log/inspect it), which can obviously
|
18
|
+
# screw up anything else back up the call stack but at least gives
|
19
|
+
# you the opportunity to catch it if you want.
|
20
|
+
#
|
21
|
+
# #create_command returns Command, which is subsequently called with
|
22
|
+
# #execute_reader(bind_values) or #execute_non_query(bind_values),
|
23
|
+
# so if the final query is what you're after, then you'll have to
|
24
|
+
# hook those both instead.
|
25
|
+
|
26
|
+
require 'spec_helper'
|
27
|
+
require 'stringio'
|
28
|
+
require 'activefacts/vocabulary'
|
29
|
+
require 'activefacts/support'
|
30
|
+
require 'activefacts/input/cql'
|
31
|
+
require 'activefacts/generate/dm'
|
32
|
+
require 'dm-core'
|
33
|
+
require 'dm-core/spec/lib/spec_helper'
|
34
|
+
|
35
|
+
describe "CQL Loader with DataMapper output" do
|
36
|
+
cql_failures = { # These CQL files can't be compiled
|
37
|
+
}
|
38
|
+
mapping_failures = { # These models can't be mapped to DM
|
39
|
+
'OrienteeringER' => 'Invalid CQL results in unmappable model',
|
40
|
+
'Insurance' => 'Cannot handle models that contain classes like Vehicle Incident with external supertypes (Incident)',
|
41
|
+
'MetamodelNext' => 'Cannot handle models that contain classes like Constraint with external supertypes (ObjectType)',
|
42
|
+
'MultiInheritance' => 'Cannot handle models that contain classes like Australian Employee with external supertypes (Australian)',
|
43
|
+
'SeparateSubtype' => 'Cannot handle models that contain classes like Vehicle Incident with external supertypes (Incident)',
|
44
|
+
'ServiceDirector' => 'Cannot handle models that contain classes like Client with external supertypes (Company)',
|
45
|
+
}
|
46
|
+
dm_failures = { # These mapped models don't work in DM
|
47
|
+
'RedundantDependency' => 'Cannot find the child_model Addres for StateOrProvince in address while finalizing StateOrProvince.address',
|
48
|
+
'Supervision' => 'Inflexion failure: Cannot find the parent_model Ceo for Company in ceo while finalizing Company.ceo',
|
49
|
+
}
|
50
|
+
|
51
|
+
# Generate and return the DataMapper models for the given vocabulary
|
52
|
+
def dm(vocabulary)
|
53
|
+
output = StringIO.new
|
54
|
+
@dumper = ActiveFacts::Generate::DM.new(vocabulary.constellation)
|
55
|
+
@dumper.generate(output)
|
56
|
+
output.rewind
|
57
|
+
output.read
|
58
|
+
end
|
59
|
+
|
60
|
+
pattern = ENV["AFTESTS"] || "*"
|
61
|
+
Dir["examples/CQL/#{pattern}.cql"].each do |cql_file|
|
62
|
+
expected_file = cql_file.sub(%r{/CQL/(.*).cql\Z}, '/datamapper/\1.dm.rb')
|
63
|
+
actual_file = cql_file.sub(%r{examples/CQL/(.*).cql\Z}, 'spec/actual/\1.dm.rb')
|
64
|
+
base = File.basename(cql_file, ".cql")
|
65
|
+
|
66
|
+
it "should load #{cql_file} and dump DataMapper models matching #{expected_file}" do
|
67
|
+
vocabulary = nil
|
68
|
+
broken = cql_failures[base]
|
69
|
+
if broken
|
70
|
+
pending(broken) {
|
71
|
+
vocabulary = ActiveFacts::Input::CQL.readfile(cql_file)
|
72
|
+
}
|
73
|
+
else
|
74
|
+
vocabulary = ActiveFacts::Input::CQL.readfile(cql_file)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Build and save the actual file:
|
78
|
+
dm_text = ''
|
79
|
+
lambda do
|
80
|
+
begin
|
81
|
+
dm_text = dm(vocabulary)
|
82
|
+
File.open(actual_file, "w") { |f| f.write dm_text }
|
83
|
+
rescue
|
84
|
+
raise unless mapping_failures[base]
|
85
|
+
end
|
86
|
+
end.should_not raise_error
|
87
|
+
|
88
|
+
if m = mapping_failures[base]
|
89
|
+
File.delete(actual_file) rescue nil
|
90
|
+
pending m
|
91
|
+
end
|
92
|
+
|
93
|
+
lambda do
|
94
|
+
begin
|
95
|
+
eval dm_text
|
96
|
+
DataMapper.finalize
|
97
|
+
rescue
|
98
|
+
raise unless dm_failures[base]
|
99
|
+
ensure
|
100
|
+
DataMapper::Spec.cleanup_models
|
101
|
+
end
|
102
|
+
end.should_not raise_error
|
103
|
+
if m = dm_failures[base]
|
104
|
+
File.delete(actual_file) rescue nil
|
105
|
+
pending m
|
106
|
+
end
|
107
|
+
|
108
|
+
pending("expected output file #{expected_file} not found") unless File.exists? expected_file
|
109
|
+
|
110
|
+
# Compare with the expected file:
|
111
|
+
expected_text = File.open(expected_file) {|f| f.read }
|
112
|
+
dm_text.should_not differ_from(expected_text)
|
113
|
+
File.delete(actual_file) # It succeeded, we don't need the file.
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|