activefacts 0.8.9 → 0.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -2,70 +2,135 @@ module ActiveFacts
2
2
  module CQL
3
3
  class Compiler < ActiveFacts::CQL::Parser
4
4
 
5
- class FactType < Concept
6
- attr_reader :fact_type
7
- attr_writer :name
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, readings, conditions = nil, returning = nil
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
- raise "Queries not yet handled: #{@source}" unless @conditions.empty? and !@returning
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 @readings
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 @readings match
25
- # * Find any existing fact type that matches any reading, or make a new one
26
- # * Add each reading that doesn't already exist in the fact type
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
- @context = CompilationContext.new(@vocabulary)
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
- verify_matching_roles # All readings of a fact type must have the same roles
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 readings:
41
- @readings.reject!{|reading| reading.is_existential_type }
42
- return true unless @readings.size > 0 # Nothing interesting was said.
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 = check_compatibility_of_matched_readings
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
- first_reading = @readings[0]
50
- @fact_type = first_reading.make_fact_type(@vocabulary)
51
- first_reading.make_reading(@vocabulary, @fact_type)
52
- first_reading.make_embedded_constraints vocabulary
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
- @existing_readings = [first_reading]
55
- elsif (n = @readings.size - @existing_readings.size) > 0
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
- new_readings = @readings - @existing_readings
61
- new_readings.each do |reading|
62
- reading.make_reading(@vocabulary, @fact_type)
63
- reading.make_embedded_constraints vocabulary
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 reading matched but the match left extra adjectives, we need to make a new RoleSequence for them:
67
- @existing_readings.each do |reading|
68
- reading.adjust_for_match
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).create_implicit_fact_types
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
- @readings.each do |reading|
83
- next unless reading.context_note
84
- reading.context_note.compile(@constellation, @fact_type)
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 check_compatibility_of_matched_readings
91
- @existing_readings = @readings.
92
- select{ |reading| reading.match_existing_fact_type @context }.
93
- sort_by{ |reading| reading.side_effects.cost }
94
- fact_types = @existing_readings.map{ |reading| reading.fact_type }.uniq.compact
95
- return nil if fact_types.empty? || @existing_readings[0].side_effects.cost != 0
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 @existing_readings[0].side_effects.cost != 0 or
99
- @existing_readings.detect{|r| r.fact_type != fact_types[0] && r.side_effects.cost == 0 }
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 readings match the chosen one instead
103
- @existing_readings.reject!{|r| r.fact_type != fact_types[0] }
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
- @readings.each do |reading|
129
- rr = reading.role_refs[-1] or next
130
- epc = rr.embedded_presence_constraint or next
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
- role_refs_by_reading_and_key = {}
158
- readings_by_role_refs =
159
- @readings.inject({}) do |hash, reading|
160
- keys = reading.role_refs.map do |rr|
161
- key = rr.key.compact
162
- role_refs_by_reading_and_key[[reading, key]] = rr
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] ||= []) << reading
269
+ (hash[keys] ||= []) << clause
167
270
  hash
168
271
  end
169
272
 
170
- if readings_by_role_refs.size != 1
171
- # Attempt loose binding here; it might merge some Compiler::RoleRefs to share the same Bindings
172
- variants = readings_by_role_refs.keys
173
- (readings_by_role_refs.size-1).downto(1) do |m| # Start with the last one
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
- readings_l = readings_by_role_refs[variants[l]]
177
- readings_m = readings_by_role_refs[variants[m]]
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
- l_role_ref = role_refs_by_reading_and_key[[readings_l[0], l_key]]
188
- m_role_ref = role_refs_by_reading_and_key[[readings_m[0], m_key]]
189
- debug :binding, "Can we match #{l_role_ref.inspect} (#{i}) with #{m_role_ref.inspect} (#{j})?"
190
- next if m_role_ref.player != l_role_ref.player
191
- if has_more_adjectives(m_role_ref, l_role_ref)
192
- debug :binding, "can rebind #{m_role_ref.inspect} to #{l_role_ref.inspect}"
193
- candidates << [m_role_ref, l_role_ref]
194
- elsif has_more_adjectives(l_role_ref, m_role_ref)
195
- debug :binding, "can rebind #{l_role_ref.inspect} to #{m_role_ref.inspect}"
196
- candidates << [l_role_ref, m_role_ref]
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 readings #{readings_l.map{|r|r.inspect}*'; '} on to #{readings_m.map{|r|r.inspect}*'; '}"
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
- readings_by_role_refs.keys.map do |keys|
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 readings already matched
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