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
|
@@ -2,70 +2,135 @@ module ActiveFacts
|
|
|
2
2
|
module CQL
|
|
3
3
|
class Compiler < ActiveFacts::CQL::Parser
|
|
4
4
|
|
|
5
|
-
class
|
|
6
|
-
attr_reader :
|
|
7
|
-
|
|
5
|
+
class Query < ObjectType # A fact type which is objectified (whether derived or not) is also an ObjectType
|
|
6
|
+
attr_reader :context # Exposed for testing purposes
|
|
7
|
+
attr_reader :conditions
|
|
8
8
|
|
|
9
|
-
def initialize name,
|
|
9
|
+
def initialize name, conditions = nil, returning = nil
|
|
10
10
|
super name
|
|
11
|
-
@readings = readings
|
|
12
11
|
@conditions = conditions
|
|
13
|
-
@returning = returning
|
|
12
|
+
@returning = returning || []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def prepare_roles clauses = nil
|
|
16
|
+
debug :binding, "preparing roles" do
|
|
17
|
+
@context ||= CompilationContext.new(@vocabulary)
|
|
18
|
+
@context.bind clauses||[], @conditions, @returning
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def compile
|
|
17
|
-
|
|
23
|
+
# Match roles with players, and match clauses with existing fact types
|
|
24
|
+
prepare_roles unless @context
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
@context.left_contraction_allowed = true
|
|
27
|
+
match_condition_fact_types
|
|
28
|
+
|
|
29
|
+
# Build the join:
|
|
30
|
+
unless @conditions.empty? and !@returning
|
|
31
|
+
debug :join, "building fact_type join" do
|
|
32
|
+
@join = build_join_nodes(@conditions.flatten)
|
|
33
|
+
@roles_by_variable = build_all_join_steps(@conditions)
|
|
34
|
+
@join.validate
|
|
35
|
+
@join
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
@context.left_contraction_allowed = false
|
|
39
|
+
@join
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def match_condition_fact_types
|
|
43
|
+
@conditions.each do |condition|
|
|
44
|
+
next if condition.is_naked_object_type
|
|
45
|
+
# REVISIT: Many conditions will imply a number of different join steps, which need to be handled (similar to nested_clauses).
|
|
46
|
+
debug :projection, "matching condition fact_type #{condition.inspect}" do
|
|
47
|
+
fact_type = condition.match_existing_fact_type @context
|
|
48
|
+
raise "Unrecognised fact type #{condition.inspect} in #{self.class}" unless fact_type
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def detect_projection_by_equality condition
|
|
54
|
+
return false unless condition.is_a?(Comparison)
|
|
55
|
+
if is_projected_role(condition.e1)
|
|
56
|
+
condition.project :left
|
|
57
|
+
elsif is_projected_role(condition.e2)
|
|
58
|
+
condition.project :right
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def is_projected_role(rr)
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class FactType < ActiveFacts::CQL::Compiler::Query
|
|
68
|
+
attr_reader :fact_type
|
|
69
|
+
attr_reader :clauses
|
|
70
|
+
attr_writer :name
|
|
71
|
+
attr_writer :pragmas
|
|
72
|
+
|
|
73
|
+
def initialize name, clauses, conditions = nil, returning = nil
|
|
74
|
+
super name, conditions, returning
|
|
75
|
+
@clauses = clauses
|
|
76
|
+
if ec = @clauses.detect{|r| r.is_equality_comparison}
|
|
77
|
+
@clauses.delete(ec)
|
|
78
|
+
@conditions.unshift(ec)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def compile
|
|
20
83
|
# Process:
|
|
21
|
-
# * Identify all role players
|
|
22
|
-
# * Match up the players in all @
|
|
84
|
+
# * Identify all role players (must be done for both clauses and conditions BEFORE matching clauses)
|
|
85
|
+
# * Match up the players in all @clauses
|
|
23
86
|
# - Be aware of multiple roles with the same player, and bind tight/loose using subscripts/role_names/adjectives
|
|
24
|
-
# - Reject the fact type unless all @
|
|
25
|
-
# * Find any existing fact type that matches any
|
|
26
|
-
# * Add each
|
|
87
|
+
# - Reject the fact type unless all @clauses match
|
|
88
|
+
# * Find any existing fact type that matches any clause, or make a new one
|
|
89
|
+
# * Add each clause that doesn't already exist in the fact type
|
|
27
90
|
# * Create any ring constraint(s)
|
|
28
91
|
# * Create embedded presence constraints
|
|
29
92
|
# * If fact type has no identifier, arrange to create the implicit one (before first use?)
|
|
30
93
|
# * Objectify the fact type if @name
|
|
31
94
|
#
|
|
32
95
|
|
|
33
|
-
|
|
34
|
-
@readings.each{ |reading| reading.identify_players_with_role_name(@context) }
|
|
35
|
-
@readings.each{ |reading| reading.identify_other_players(@context) }
|
|
36
|
-
@readings.each{ |reading| reading.bind_roles @context } # Create the Compiler::Bindings
|
|
96
|
+
prepare_roles @clauses
|
|
37
97
|
|
|
38
|
-
|
|
98
|
+
# REVISIT: Compiling the conditions here make it impossible to define a self-referential (transitive) query.
|
|
99
|
+
return super if @clauses.empty? # It's a query
|
|
39
100
|
|
|
40
|
-
# Ignore any useless
|
|
41
|
-
@
|
|
42
|
-
return true unless @
|
|
101
|
+
# Ignore any useless clauses:
|
|
102
|
+
@clauses.reject!{|clause| clause.is_existential_type }
|
|
103
|
+
return true unless @clauses.size > 0 # Nothing interesting was said.
|
|
43
104
|
|
|
44
105
|
# See if any existing fact type is being invoked (presumably to objectify or extend it)
|
|
45
|
-
@fact_type =
|
|
106
|
+
@fact_type = check_compatibility_of_matched_clauses
|
|
107
|
+
|
|
108
|
+
verify_matching_roles # All clauses of a fact type must have the same roles
|
|
46
109
|
|
|
47
110
|
if !@fact_type
|
|
48
111
|
# Make a new fact type:
|
|
49
|
-
|
|
50
|
-
@fact_type =
|
|
51
|
-
|
|
52
|
-
|
|
112
|
+
first_clause = @clauses[0]
|
|
113
|
+
@fact_type = first_clause.make_fact_type(@vocabulary)
|
|
114
|
+
first_clause.make_reading(@vocabulary, @fact_type)
|
|
115
|
+
first_clause.make_embedded_constraints vocabulary
|
|
53
116
|
@fact_type.create_implicit_fact_type_for_unary if @fact_type.all_role.size == 1 && !@name
|
|
54
|
-
@
|
|
55
|
-
elsif (n = @
|
|
117
|
+
@existing_clauses = [first_clause]
|
|
118
|
+
elsif (n = @clauses.size - @existing_clauses.size) > 0
|
|
56
119
|
debug :binding, "Extending existing fact type with #{n} new readings"
|
|
57
120
|
end
|
|
58
121
|
|
|
59
122
|
# Now make any new readings:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
123
|
+
new_clauses = @clauses - @existing_clauses
|
|
124
|
+
new_clauses.each do |clause|
|
|
125
|
+
clause.make_reading(@vocabulary, @fact_type)
|
|
126
|
+
clause.make_embedded_constraints vocabulary
|
|
64
127
|
end
|
|
65
128
|
|
|
66
|
-
# If a
|
|
67
|
-
@
|
|
68
|
-
|
|
129
|
+
# If a clause matched but the match left extra adjectives, we need to make a new RoleSequence for them:
|
|
130
|
+
@existing_clauses.each do |clause|
|
|
131
|
+
clause.adjust_for_match
|
|
132
|
+
# Add any new constraints that we found in the match (presence, ring, etc)
|
|
133
|
+
clause.make_embedded_constraints(vocabulary)
|
|
69
134
|
end
|
|
70
135
|
|
|
71
136
|
# Objectify the fact type if necessary:
|
|
@@ -73,34 +138,72 @@ module ActiveFacts
|
|
|
73
138
|
if @fact_type.entity_type and @name != @fact_type.entity_type.name
|
|
74
139
|
raise "Cannot objectify fact type as #{@name} and as #{@fact_type.entity_type.name}"
|
|
75
140
|
end
|
|
76
|
-
@constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type)
|
|
141
|
+
e = @constellation.EntityType(@vocabulary, @name, :fact_type => @fact_type)
|
|
142
|
+
e.create_implicit_fact_types
|
|
143
|
+
if @pragmas
|
|
144
|
+
e.is_independent = true if @pragmas.delete('independent')
|
|
145
|
+
end
|
|
146
|
+
if @pragmas && @pragmas.size > 0
|
|
147
|
+
$stderr.puts "Mapping pragmas #{@pragmas.inspect} are ignored for objectified fact type #{@name}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@clauses.each do |clause|
|
|
152
|
+
next unless clause.context_note
|
|
153
|
+
clause.context_note.compile(@constellation, @fact_type)
|
|
77
154
|
end
|
|
78
155
|
|
|
79
156
|
# REVISIT: This isn't the thing to do long term; it needs to be added later only if we find no other constraint
|
|
80
|
-
make_default_identifier_for_fact_type
|
|
157
|
+
make_default_identifier_for_fact_type if @conditions.empty?
|
|
81
158
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
159
|
+
# Compile the conditions:
|
|
160
|
+
super
|
|
161
|
+
unless @conditions.empty?
|
|
162
|
+
@clauses.each do |clause|
|
|
163
|
+
project_clause_roles(clause)
|
|
164
|
+
end
|
|
85
165
|
end
|
|
86
166
|
|
|
87
167
|
@fact_type
|
|
88
168
|
end
|
|
89
169
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
170
|
+
def project_clause_roles(clause)
|
|
171
|
+
# Attach the clause's role references to the projected roles of the join
|
|
172
|
+
clause.var_refs.each_with_index do |var_ref, i|
|
|
173
|
+
role, join_role = @roles_by_variable[var_ref.variable]
|
|
174
|
+
raise "#{var_ref} must be a role projected from the conditions" unless role
|
|
175
|
+
raise "#{var_ref} has already-projected join role!" if join_role.role_ref
|
|
176
|
+
var_ref.role_ref.join_role = join_role
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# A Comparison in the conditions which projects a role is not treated as a comparison, just as projection
|
|
181
|
+
def is_projected_role(rr)
|
|
182
|
+
# rr is a RoleRef on one side of the comparison.
|
|
183
|
+
# If its binding contains a reference from our readings, it's projected.
|
|
184
|
+
rr.variable.refs.detect do |ref|
|
|
185
|
+
@readings.include?(ref.reading)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def check_compatibility_of_matched_clauses
|
|
190
|
+
# REVISIT: If we have conditions, we must match all given clauses exactly (no side-effects)
|
|
191
|
+
@existing_clauses = @clauses.
|
|
192
|
+
select{ |clause| clause.match_existing_fact_type @context }.
|
|
193
|
+
sort_by{ |clause| clause.side_effects.cost }
|
|
194
|
+
fact_types = @existing_clauses.map{ |clause| clause.fact_type }.uniq.compact
|
|
195
|
+
|
|
196
|
+
return nil if fact_types.empty?
|
|
197
|
+
# If there's only a single clause, the match must be exact:
|
|
198
|
+
return nil if @clauses.size == 1 && @existing_clauses[0].side_effects.cost != 0
|
|
96
199
|
if (fact_types.size > 1)
|
|
97
200
|
# There must be only one fact type with exact matches:
|
|
98
|
-
if @
|
|
99
|
-
@
|
|
201
|
+
if @existing_clauses[0].side_effects.cost != 0 or
|
|
202
|
+
@existing_clauses.detect{|r| r.fact_type != fact_types[0] && r.side_effects.cost == 0 }
|
|
100
203
|
raise "Clauses match different existing fact types '#{fact_types.map{|ft| ft.preferred_reading.expand}*"', '"}'"
|
|
101
204
|
end
|
|
102
|
-
# Try to make false-matched
|
|
103
|
-
@
|
|
205
|
+
# Try to make false-matched clauses match the chosen one instead
|
|
206
|
+
@existing_clauses.reject!{|r| r.fact_type != fact_types[0] }
|
|
104
207
|
end
|
|
105
208
|
fact_types[0]
|
|
106
209
|
end
|
|
@@ -125,9 +228,9 @@ module ActiveFacts
|
|
|
125
228
|
end
|
|
126
229
|
|
|
127
230
|
# If there's an existing presence constraint that can be converted into a PC, do that:
|
|
128
|
-
@
|
|
129
|
-
|
|
130
|
-
epc =
|
|
231
|
+
@clauses.each do |clause|
|
|
232
|
+
var_ref = clause.var_refs[-1] or next
|
|
233
|
+
epc = var_ref.embedded_presence_constraint or next
|
|
131
234
|
epc.max_frequency == 1 or next
|
|
132
235
|
next if epc.enforcement
|
|
133
236
|
epc.is_preferred_identifier = true
|
|
@@ -154,27 +257,27 @@ module ActiveFacts
|
|
|
154
257
|
end
|
|
155
258
|
|
|
156
259
|
def verify_matching_roles
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@
|
|
160
|
-
keys =
|
|
161
|
-
key =
|
|
162
|
-
|
|
260
|
+
var_refs_by_clause_and_key = {}
|
|
261
|
+
clauses_by_var_refs =
|
|
262
|
+
@clauses.inject({}) do |hash, clause|
|
|
263
|
+
keys = clause.var_refs.map do |var_ref|
|
|
264
|
+
key = var_ref.key.compact
|
|
265
|
+
var_refs_by_clause_and_key[[clause, key]] = var_ref
|
|
163
266
|
key
|
|
164
267
|
end.sort_by{|a| a.map{|k|k.to_s}}
|
|
165
268
|
raise "Fact types may not have duplicate roles" if keys.uniq.size < keys.size
|
|
166
|
-
(hash[keys] ||= []) <<
|
|
269
|
+
(hash[keys] ||= []) << clause
|
|
167
270
|
hash
|
|
168
271
|
end
|
|
169
272
|
|
|
170
|
-
if
|
|
171
|
-
# Attempt loose binding here; it might merge some Compiler::
|
|
172
|
-
variants =
|
|
173
|
-
(
|
|
273
|
+
if clauses_by_var_refs.size != 1 and @conditions.empty?
|
|
274
|
+
# Attempt loose binding here; it might merge some Compiler::VarRefs to share the same Variables
|
|
275
|
+
variants = clauses_by_var_refs.keys
|
|
276
|
+
(clauses_by_var_refs.size-1).downto(1) do |m| # Start with the last one
|
|
174
277
|
0.upto(m-1) do |l| # Try to rebind onto any lower one
|
|
175
278
|
common = variants[m]&variants[l]
|
|
176
|
-
|
|
177
|
-
|
|
279
|
+
clauses_l = clauses_by_var_refs[variants[l]]
|
|
280
|
+
clauses_m = clauses_by_var_refs[variants[m]]
|
|
178
281
|
l_keys = variants[l]-common
|
|
179
282
|
m_keys = variants[m]-common
|
|
180
283
|
debug :binding, "Try to collapse variant #{m} onto #{l}; diffs are #{l_keys.inspect} -> #{m_keys.inspect}"
|
|
@@ -184,16 +287,16 @@ module ActiveFacts
|
|
|
184
287
|
candidates = []
|
|
185
288
|
(0...m_keys.size).each do |j|
|
|
186
289
|
m_key = m_keys[j]
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
debug :binding, "Can we match #{
|
|
190
|
-
next if
|
|
191
|
-
if has_more_adjectives(
|
|
192
|
-
debug :binding, "can rebind #{
|
|
193
|
-
candidates << [
|
|
194
|
-
elsif has_more_adjectives(
|
|
195
|
-
debug :binding, "can rebind #{
|
|
196
|
-
candidates << [
|
|
290
|
+
l_var_ref = var_refs_by_clause_and_key[[clauses_l[0], l_key]]
|
|
291
|
+
m_var_ref = var_refs_by_clause_and_key[[clauses_m[0], m_key]]
|
|
292
|
+
debug :binding, "Can we match #{l_var_ref.inspect} (#{i}) with #{m_var_ref.inspect} (#{j})?"
|
|
293
|
+
next if m_var_ref.player != l_var_ref.player
|
|
294
|
+
if has_more_adjectives(m_var_ref, l_var_ref)
|
|
295
|
+
debug :binding, "can rebind #{m_var_ref.inspect} to #{l_var_ref.inspect}"
|
|
296
|
+
candidates << [m_var_ref, l_var_ref]
|
|
297
|
+
elsif has_more_adjectives(l_var_ref, m_var_ref)
|
|
298
|
+
debug :binding, "can rebind #{l_var_ref.inspect} to #{m_var_ref.inspect}"
|
|
299
|
+
candidates << [l_var_ref, m_var_ref]
|
|
197
300
|
end
|
|
198
301
|
end
|
|
199
302
|
|
|
@@ -207,12 +310,12 @@ module ActiveFacts
|
|
|
207
310
|
end
|
|
208
311
|
if (rebindings == l_keys.size)
|
|
209
312
|
# Successfully rebound this fact type
|
|
210
|
-
debug :binding, "Successfully rebound
|
|
313
|
+
debug :binding, "Successfully rebound clauses #{clauses_l.map{|r|r.inspect}*'; '} on to #{clauses_m.map{|r|r.inspect}*'; '}"
|
|
211
314
|
break
|
|
212
315
|
else
|
|
213
316
|
# No point continuing, we failed on this one.
|
|
214
317
|
raise "All readings in a fact type definition must have matching role players, compare (#{
|
|
215
|
-
|
|
318
|
+
clauses_by_var_refs.keys.map do |keys|
|
|
216
319
|
keys.map{|key| key*'-' }*", "
|
|
217
320
|
end*") with ("
|
|
218
321
|
})"
|
|
@@ -220,11 +323,25 @@ module ActiveFacts
|
|
|
220
323
|
|
|
221
324
|
end
|
|
222
325
|
end
|
|
223
|
-
# else all
|
|
326
|
+
# else all clauses already matched
|
|
224
327
|
end
|
|
225
328
|
end
|
|
226
|
-
end
|
|
227
329
|
|
|
330
|
+
def to_s
|
|
331
|
+
if @conditions.size > 0
|
|
332
|
+
true
|
|
333
|
+
end
|
|
334
|
+
"FactType: #{(s = super and !s.empty?) ? "#{s} " : '' }#{@clauses.inspect}" +
|
|
335
|
+
if @conditions && !@conditions.empty?
|
|
336
|
+
" where "+@conditions.map{|c| ((j=c.conjunction) ? j+' ' : '') + c.to_s}*' '
|
|
337
|
+
else
|
|
338
|
+
''
|
|
339
|
+
end +
|
|
340
|
+
(@pragmas && @pragmas.size > 0 ? ", pragmas [#{@pragmas.sort*','}]" : '')
|
|
341
|
+
|
|
342
|
+
# REVISIT: @returning = returning
|
|
343
|
+
end
|
|
344
|
+
end
|
|
228
345
|
end
|
|
229
346
|
end
|
|
230
347
|
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module ActiveFacts
|
|
2
|
+
module CQL
|
|
3
|
+
class Compiler < ActiveFacts::CQL::Parser
|
|
4
|
+
class Definition
|
|
5
|
+
# Make a JoinNode for every variable present in these clauses
|
|
6
|
+
def build_join_nodes(clauses_list)
|
|
7
|
+
debug :join, "Building join nodes" do
|
|
8
|
+
join = @constellation.Join(:new)
|
|
9
|
+
all_variables_in_clauses(clauses_list).
|
|
10
|
+
each do |variable|
|
|
11
|
+
debug :join, "Creating join node #{join.all_join_node.size} for #{variable.inspect}"
|
|
12
|
+
variable.join_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => variable.player)
|
|
13
|
+
if literal = variable.refs.detect{|r| r.literal}
|
|
14
|
+
unit = @constellation.Unit.detect{|k, v| [v.name, v.plural_name].include? literal.unit} if literal.unit
|
|
15
|
+
variable.join_node.value = [literal.literal.to_s, literal.is_a?(String), unit]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
join
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_all_join_steps(clauses_list)
|
|
23
|
+
roles_by_variable = {}
|
|
24
|
+
debug :join, "Building join steps" do
|
|
25
|
+
clauses_list.each do |clause|
|
|
26
|
+
next if clause.is_naked_object_type
|
|
27
|
+
build_join_steps(clause, roles_by_variable)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
roles_by_variable
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def build_join_steps clause, roles_by_variable = {}, objectification_node = nil
|
|
34
|
+
join_roles = []
|
|
35
|
+
incidental_roles = []
|
|
36
|
+
debug :join, "Creating join Role Sequence for #{clause.inspect} with #{clause.var_refs.size} role refs" do
|
|
37
|
+
objectification_step = nil
|
|
38
|
+
clause.var_refs.each do |var_ref|
|
|
39
|
+
# These var_refs are the Compiler::VarRefs, which have associated Metamodel::RoleRefs,
|
|
40
|
+
# but we need to create JoinRoles for those roles.
|
|
41
|
+
# REVISIT: JoinRoles may need to save residual_adjectives
|
|
42
|
+
variable = var_ref.variable
|
|
43
|
+
role = (var_ref && var_ref.role) || (var_ref.role_ref && var_ref.role_ref.role)
|
|
44
|
+
join_role = nil
|
|
45
|
+
|
|
46
|
+
debugger unless clause.fact_type
|
|
47
|
+
if (clause.fact_type.entity_type)
|
|
48
|
+
# This clause is of an objectified fact type.
|
|
49
|
+
# We need a join step from this role to the phantom role, but not
|
|
50
|
+
# for a role that has only one var_ref (this one) in their variable.
|
|
51
|
+
# Create the JoinNode and JoinRole in any case though.
|
|
52
|
+
refs_count = variable.refs.size
|
|
53
|
+
objectification_ref_count = 0
|
|
54
|
+
if var_ref.nested_clauses
|
|
55
|
+
var_ref.nested_clauses.each do |ojc|
|
|
56
|
+
objectification_ref_count += ojc.var_refs.select{|var_ref| var_ref.variable.refs.size > 1}.size
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
refs_count += objectification_ref_count
|
|
60
|
+
|
|
61
|
+
debug :join, "Creating Join Node #{var_ref.inspect} (counts #{refs_count}/#{objectification_ref_count}) and objectification Join Step for #{var_ref.inspect}" do
|
|
62
|
+
|
|
63
|
+
raise "Internal error: Trying to add role of #{role.object_type.name} to join node for #{variable.join_node.object_type.name}" unless variable.join_node.object_type == role.object_type
|
|
64
|
+
join_role = @constellation.JoinRole(variable.join_node, role)
|
|
65
|
+
|
|
66
|
+
if (refs_count <= 1) # Our work here is done if there are no other refs
|
|
67
|
+
if objectification_step
|
|
68
|
+
join_role.join_step = objectification_step
|
|
69
|
+
else
|
|
70
|
+
incidental_roles << join_role
|
|
71
|
+
end
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
join_roles << join_role
|
|
76
|
+
unless objectification_node
|
|
77
|
+
# This is an implicit objectification, just the FT clause, not ET(where ...clause...)
|
|
78
|
+
# We need to create a JoinNode for this object, even though it has no VarRefs
|
|
79
|
+
join = variable.join_node.join
|
|
80
|
+
debug :join, "Creating JN#{join.all_join_node.size} for #{clause.fact_type.entity_type.name} in objectification"
|
|
81
|
+
objectification_node = @constellation.JoinNode(join, join.all_join_node.size, :object_type => clause.fact_type.entity_type)
|
|
82
|
+
end
|
|
83
|
+
raise "Internal error: Trying to add role of #{role.implicit_fact_type.all_role.single.object_type.name} to join node for #{objectification_node.object_type.name}" unless objectification_node.object_type == role.implicit_fact_type.all_role.single.object_type
|
|
84
|
+
|
|
85
|
+
irole = role.implicit_fact_type.all_role.single
|
|
86
|
+
raise "Internal error: Trying to add role of #{irole.object_type.name} to join node for #{objectification_node.object_type.name}" unless objectification_node.object_type == irole.object_type
|
|
87
|
+
objectification_role = @constellation.JoinRole(objectification_node, role.implicit_fact_type.all_role.single)
|
|
88
|
+
objectification_step = @constellation.JoinStep(objectification_role, join_role, :fact_type => role.implicit_fact_type)
|
|
89
|
+
debug :join, "New #{objectification_step.describe}"
|
|
90
|
+
debug :join, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{objectification_step.describe}" if incidental_roles.size > 0
|
|
91
|
+
incidental_roles.each { |jr| jr.join_step = objectification_step }
|
|
92
|
+
incidental_roles = []
|
|
93
|
+
join_roles = []
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
debug :join, "Creating VarRef for #{var_ref.inspect}" do
|
|
97
|
+
# REVISIT: If there's an implicit subtyping join here, create it; then always raise the error here.
|
|
98
|
+
# I don't want to do this for now because the verbaliser will always verbalise all join steps.
|
|
99
|
+
if variable.join_node.object_type != role.object_type and
|
|
100
|
+
0 == (variable.join_node.object_type.supertypes_transitive & role.object_type.supertypes_transitive).size
|
|
101
|
+
raise "Internal error: Trying to add role of #{role.object_type.name} to join node #{variable.join_node.ordinal} for #{variable.join_node.object_type.name} in '#{clause.fact_type.default_reading}'"
|
|
102
|
+
end
|
|
103
|
+
raise "Internal error: Trying to add role of #{role.object_type.name} to join node #{variable.join_node.ordinal} for #{variable.join_node.object_type.name}" unless variable.join_node.object_type == role.object_type
|
|
104
|
+
begin
|
|
105
|
+
join_role = @constellation.JoinRole(variable.join_node, role)
|
|
106
|
+
rescue ArgumentError => e
|
|
107
|
+
join_role = @constellation.JoinRole(variable.join_node, role)
|
|
108
|
+
end
|
|
109
|
+
join_roles << join_role
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if var_ref.nested_clauses
|
|
114
|
+
# We are looking at a role whose player is an objectification of a fact type,
|
|
115
|
+
# which will have ImplicitFactTypes for each role.
|
|
116
|
+
# Each of these ImplicitFactTypes has a single phantom role played by the objectifying entity type
|
|
117
|
+
# One of these phantom roles is likely to be the subject of an objectification join step.
|
|
118
|
+
var_ref.nested_clauses.each do |r|
|
|
119
|
+
debug :join, "Building objectification join for #{var_ref.nested_clauses.inspect}" do
|
|
120
|
+
build_join_steps r, roles_by_variable, variable.join_node
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
roles_by_variable[variable] = [role, join_role]
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if join_roles.size > 0
|
|
129
|
+
end_node = join_roles[-1].join_node
|
|
130
|
+
if !clause.fact_type.entity_type and role = clause.fact_type.all_role.single
|
|
131
|
+
# Don't give the ImplicitBoolean a join_node. We can live without one, for now.
|
|
132
|
+
# The Join Step will have a duplicate node, and the fact type will tell us what's happening
|
|
133
|
+
join_roles << join_roles[0]
|
|
134
|
+
end
|
|
135
|
+
# We aren't talking about objectification here, so there must be exactly two roles.
|
|
136
|
+
raise "REVISIT: Internal error constructing join for #{clause.inspect}" if join_roles.size != 2
|
|
137
|
+
js = @constellation.JoinStep(join_roles[0], join_roles[1], :fact_type => clause.fact_type)
|
|
138
|
+
debug :join, "New Join Step #{js.describe}"
|
|
139
|
+
debug :join, "Associating #{incidental_roles.map(&:describe)*', '} incidental roles with #{js.describe}" if incidental_roles.size > 0
|
|
140
|
+
incidental_roles.each { |jr| jr.join_step = js }
|
|
141
|
+
end
|
|
142
|
+
roles_by_variable
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Return the unique array of all variables in these clauses, including in objectification joins
|
|
146
|
+
def all_variables_in_clauses clauses
|
|
147
|
+
clauses.map do |clause|
|
|
148
|
+
clause.var_refs.map do |var_ref|
|
|
149
|
+
raise "Variable reference #{var_ref.inspect} is not bound to a variable" unless var_ref.variable
|
|
150
|
+
[var_ref.variable] + (var_ref.nested_clauses ? all_variables_in_clauses(var_ref.nested_clauses) : [])
|
|
151
|
+
end
|
|
152
|
+
end.
|
|
153
|
+
flatten.
|
|
154
|
+
uniq
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|