activefacts 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -0
- data/examples/CQL/Metamodel.cql +5 -0
- data/lib/activefacts/cql/compiler.rb +25 -2
- data/lib/activefacts/cql/compiler/constraint.rb +9 -1
- data/lib/activefacts/cql/compiler/fact_type.rb +1 -0
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/dependency_analyser.rb +182 -0
- data/lib/activefacts/generate/composition.rb +118 -0
- data/lib/activefacts/generate/cql.rb +3 -1
- data/lib/activefacts/generate/helpers/inject.rb +16 -0
- data/lib/activefacts/generate/rails/models.rb +1 -1
- data/lib/activefacts/generate/stats.rb +69 -0
- data/lib/activefacts/generate/topics.rb +265 -0
- data/lib/activefacts/generate/traits/oo.rb +73 -0
- data/lib/activefacts/generate/traits/ordered.rb +33 -0
- data/lib/activefacts/generate/traits/ruby.rb +210 -0
- data/lib/activefacts/input/orm.rb +158 -121
- data/lib/activefacts/persistence/columns.rb +1 -11
- data/lib/activefacts/persistence/foreignkey.rb +5 -6
- data/lib/activefacts/persistence/reference.rb +21 -1
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +236 -8
- data/lib/activefacts/vocabulary/metamodel.rb +11 -0
- data/lib/activefacts/vocabulary/query_evaluator.rb +304 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4c7be7ca9e116d36232a6e9c0bcf28f0f4a63eb
|
4
|
+
data.tar.gz: a3c13e8e437aa028760eff29cd05df07b0017a91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49878c205424a5534e811af19bf09ebe0e3c8aefbd3127a1ee0695d3a5c77eec2beb74715455ff32d7bde613e8a1eff8241dc35ba77a79915770e1ee68c60ba0
|
7
|
+
data.tar.gz: 4396ee06e34a48236e858274010356123f66847686a04763b4b4992283ae30f8dd6b0d96e7086803339a97f9ffbc2c158d2563a8840a288c330623f11680de8b
|
data/Manifest.txt
CHANGED
@@ -71,10 +71,13 @@ lib/activefacts/cql/compiler/shared.rb
|
|
71
71
|
lib/activefacts/cql/compiler/value_type.rb
|
72
72
|
lib/activefacts/cql/nodes.rb
|
73
73
|
lib/activefacts/cql/parser.rb
|
74
|
+
lib/activefacts/dependency_analyser.rb
|
74
75
|
lib/activefacts/generate/absorption.rb
|
76
|
+
lib/activefacts/generate/composition.rb
|
75
77
|
lib/activefacts/generate/cql.rb
|
76
78
|
lib/activefacts/generate/dm.rb
|
77
79
|
lib/activefacts/generate/help.rb
|
80
|
+
lib/activefacts/generate/helpers/inject.rb
|
78
81
|
lib/activefacts/generate/helpers/oo.rb
|
79
82
|
lib/activefacts/generate/helpers/ordered.rb
|
80
83
|
lib/activefacts/generate/helpers/rails.rb
|
@@ -85,9 +88,14 @@ lib/activefacts/generate/records.rb
|
|
85
88
|
lib/activefacts/generate/ruby.rb
|
86
89
|
lib/activefacts/generate/sql/mysql.rb
|
87
90
|
lib/activefacts/generate/sql/server.rb
|
91
|
+
lib/activefacts/generate/stats.rb
|
88
92
|
lib/activefacts/generate/rails/schema.rb
|
89
93
|
lib/activefacts/generate/rails/models.rb
|
90
94
|
lib/activefacts/generate/text.rb
|
95
|
+
lib/activefacts/generate/topics.rb
|
96
|
+
lib/activefacts/generate/traits/oo.rb
|
97
|
+
lib/activefacts/generate/traits/ordered.rb
|
98
|
+
lib/activefacts/generate/traits/ruby.rb
|
91
99
|
lib/activefacts/generate/transform/surrogate.rb
|
92
100
|
lib/activefacts/generate/version.rb
|
93
101
|
lib/activefacts/input/cql.rb
|
@@ -107,6 +115,7 @@ lib/activefacts/vocabulary.rb
|
|
107
115
|
lib/activefacts/vocabulary/extensions.rb
|
108
116
|
lib/activefacts/vocabulary/metamodel.rb
|
109
117
|
lib/activefacts/vocabulary/verbaliser.rb
|
118
|
+
lib/activefacts/vocabulary/query_evaluator.rb
|
110
119
|
script/txt2html
|
111
120
|
spec/cql/comparison_spec.rb
|
112
121
|
spec/cql/contractions_spec.rb
|
data/examples/CQL/Metamodel.cql
CHANGED
@@ -31,6 +31,7 @@ Rotation Setting is written as String restricted to {'left', 'right'};
|
|
31
31
|
Scale is written as Unsigned Integer(32);
|
32
32
|
Subscript is written as Unsigned Integer(16);
|
33
33
|
Text is written as String(256);
|
34
|
+
Topic Name is written as Name;
|
34
35
|
Transaction Phase is written as String restricted to {'assert', 'commit'};
|
35
36
|
X is written as Signed Integer(32);
|
36
37
|
Y is written as Signed Integer(32);
|
@@ -165,6 +166,10 @@ Subset Constraint is a kind of Set Constraint;
|
|
165
166
|
Subset Constraint covers one subset-Role Sequence;
|
166
167
|
Subset Constraint covers one superset-Role Sequence;
|
167
168
|
|
169
|
+
Topic is identified by its Name;
|
170
|
+
Concept belongs to at most one Topic,
|
171
|
+
Topic contains Concept;
|
172
|
+
|
168
173
|
Unit is identified by Concept where
|
169
174
|
Unit is an instance of one Concept;
|
170
175
|
Ephemera URL provides Unit coefficient,
|
@@ -56,6 +56,15 @@ module ActiveFacts
|
|
56
56
|
extend language_module
|
57
57
|
end
|
58
58
|
|
59
|
+
# Mark any new Concepts as belonging to this topic
|
60
|
+
def topic_flood
|
61
|
+
@constellation.Concept.each do |key, concept|
|
62
|
+
next if concept.topic
|
63
|
+
trace :topic, "Colouring #{concept.describe} with #{@topic.topic_name}"
|
64
|
+
concept.topic = @topic
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
59
68
|
def compile input
|
60
69
|
include_language
|
61
70
|
|
@@ -75,7 +84,14 @@ module ActiveFacts
|
|
75
84
|
ast.vocabulary = @vocabulary
|
76
85
|
value = compile_definition ast
|
77
86
|
trace :definition, "Compiled to #{value.is_a?(Array) ? value.map{|v| v.verbalise}*', ' : value.verbalise}" if value
|
78
|
-
|
87
|
+
if value.is_a?(ActiveFacts::Metamodel::Topic)
|
88
|
+
topic_flood if @topic
|
89
|
+
@topic = value
|
90
|
+
elsif ast.is_a?(Compiler::Vocabulary)
|
91
|
+
topic_flood if @topic
|
92
|
+
@vocabulary = value
|
93
|
+
@topic = @constellation.Topic(@vocabulary.name)
|
94
|
+
end
|
79
95
|
rescue => e
|
80
96
|
# Augment the exception message, but preserve the backtrace
|
81
97
|
start_line = @string.line_of(node.interval.first)
|
@@ -86,6 +102,7 @@ module ActiveFacts
|
|
86
102
|
raise ne
|
87
103
|
end
|
88
104
|
end
|
105
|
+
topic_flood if @topic
|
89
106
|
end
|
90
107
|
raise failure_reason unless ok
|
91
108
|
vocabulary
|
@@ -96,12 +113,18 @@ module ActiveFacts
|
|
96
113
|
saved_block = @block
|
97
114
|
saved_string = @string
|
98
115
|
saved_input_length = @input_length
|
116
|
+
saved_topic = @topic
|
99
117
|
old_filename = @filename
|
100
118
|
@filename = File.dirname(old_filename)+'/'+file+'.cql'
|
101
119
|
|
102
120
|
# REVISIT: Save and use another @vocabulary for this file?
|
103
121
|
File.open(@filename) do |f|
|
104
|
-
|
122
|
+
topic_flood if @topic
|
123
|
+
@topic = @constellation.Topic(File.basename(@filename, '.cql'))
|
124
|
+
trace :import, "Importing #{@filename} as #{@topic.topic_name}" do
|
125
|
+
ok = parse_all(f.read, nil, &@block)
|
126
|
+
end
|
127
|
+
@topic = saved_topic
|
105
128
|
end
|
106
129
|
|
107
130
|
rescue => e
|
@@ -47,8 +47,16 @@ module ActiveFacts
|
|
47
47
|
|
48
48
|
class Constraint < Definition
|
49
49
|
def initialize context_note, enforcement, clauses_lists = []
|
50
|
-
if context_note.is_a?(Treetop::Runtime::SyntaxNode)
|
50
|
+
if context_note.is_a?(Treetop::Runtime::SyntaxNode) && !context_note.empty?
|
51
51
|
context_note = context_note.empty? ? nil : context_note.ast
|
52
|
+
else
|
53
|
+
context_note = nil # Perhaps a context note got attached to one of the clauses. Steal it.
|
54
|
+
clauses_lists.detect do |clauses_list|
|
55
|
+
if c = clauses_list.last.context_note
|
56
|
+
context_note = c
|
57
|
+
clauses_list.last.context_note = nil
|
58
|
+
end
|
59
|
+
end
|
52
60
|
end
|
53
61
|
@context_note = context_note
|
54
62
|
@enforcement = enforcement
|
@@ -323,6 +323,7 @@ module ActiveFacts
|
|
323
323
|
:max_frequency => 1,
|
324
324
|
:is_preferred_identifier => true # (prefer || !!@fact_type.entity_type)
|
325
325
|
)
|
326
|
+
pc.concept.topic = @fact_type.concept.topic
|
326
327
|
trace :constraint, "Made new fact type implicit PC GUID=#{pc.concept.guid} #{pc.name} min=nil max=1 over #{rs.describe}"
|
327
328
|
elsif pc
|
328
329
|
trace :constraint, "Will rely on existing UC GUID=#{pc.concept.guid} #{pc.name} to be used as PI over #{rs.describe}"
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module ActiveFacts
|
2
|
+
class DependencyAnalyser
|
3
|
+
def initialize enumerable, &block
|
4
|
+
@enumerable = enumerable
|
5
|
+
analyse_precursors &block
|
6
|
+
end
|
7
|
+
|
8
|
+
def analyse_precursors &block
|
9
|
+
@precursors = {}
|
10
|
+
@enumerable.each do |item|
|
11
|
+
@precursors[item] = block.call(item)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def analyse_precursors_transitive
|
16
|
+
all_precursors = proc do |item|
|
17
|
+
p = @precursors[item]
|
18
|
+
all =
|
19
|
+
p + p.map do |precursor|
|
20
|
+
p.include?(precursor) ? [] : all_precursors.call(precursor)
|
21
|
+
end.flatten
|
22
|
+
all.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
@precursors_transitive = {}
|
26
|
+
@enumerable.each do |item|
|
27
|
+
@precursors_transitive[item] = all_precursors.call(item)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def analyse_followers
|
32
|
+
@followers = Hash.new{|h, k| h[k] = [] }
|
33
|
+
@enumerable.each do |item|
|
34
|
+
@precursors[item].each do |precursor|
|
35
|
+
@followers[precursor] << item
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def analyse_chasers
|
41
|
+
analyse_precursors_transitive unless @precursors_transitive
|
42
|
+
analyse_followers unless @followers
|
43
|
+
|
44
|
+
# A follower is an object with us as a precursor, that has no new precursors of its own
|
45
|
+
@chasers = {}
|
46
|
+
@enumerable.each do |item|
|
47
|
+
@chasers[item] =
|
48
|
+
@enumerable.select do |follower|
|
49
|
+
@precursors[follower].include?(item) and
|
50
|
+
(@precursors_transitive[follower] - @precursors_transitive[item] - [item]).size == 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def tsort &block
|
56
|
+
analyse_precursors unless @precursors
|
57
|
+
emitted = {}
|
58
|
+
pass = 0
|
59
|
+
until emitted.size == @enumerable.size
|
60
|
+
next_items = []
|
61
|
+
blocked =
|
62
|
+
@enumerable.inject({}) do |hash, item|
|
63
|
+
next hash if emitted[item]
|
64
|
+
blockers = item.precursors.select{|precursor| !emitted[precursor]}
|
65
|
+
if blockers.size > 0
|
66
|
+
hash[item] = blockers
|
67
|
+
else
|
68
|
+
next_items << item
|
69
|
+
end
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
return blocked if next_items.size == 0 # Cannot make progress
|
73
|
+
# puts "PASS #{pass += 1}"
|
74
|
+
next_items.each do |item|
|
75
|
+
block.call(item)
|
76
|
+
emitted[item] = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def each &b
|
83
|
+
if block_given?
|
84
|
+
@enumerable.each { |item| yield item}
|
85
|
+
else
|
86
|
+
@enumerable
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def precursors item = nil, &b
|
91
|
+
analyse_precursors unless @precursors
|
92
|
+
if item
|
93
|
+
if block_given?
|
94
|
+
Array(@precursors[item]).each { |precursor| yield precursor, item }
|
95
|
+
else
|
96
|
+
Array(@precursors[item])
|
97
|
+
end
|
98
|
+
else
|
99
|
+
@enumerable.each do |item|
|
100
|
+
precursors(item, &b)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def precursors_transitive item, &b
|
106
|
+
analyse_precursors_transitive unless @precursors_transitive
|
107
|
+
if item
|
108
|
+
if block_given?
|
109
|
+
Array(@precursors_transitive[item]).each { |precursor| yield precursor, item }
|
110
|
+
else
|
111
|
+
Array(@precursors_transitive[item])
|
112
|
+
end
|
113
|
+
else
|
114
|
+
@enumerable.each do |item|
|
115
|
+
precursors_transitive(item, &b)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def followers item = nil, &b
|
121
|
+
analyse_followers unless @followers
|
122
|
+
if item
|
123
|
+
if block_given?
|
124
|
+
Array(@followers[item]).each { |follower| yield follower, item }
|
125
|
+
else
|
126
|
+
Array(@followers[item])
|
127
|
+
end
|
128
|
+
else
|
129
|
+
@enumerable.each do |item|
|
130
|
+
followers(item, &b)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def chasers item, &b
|
136
|
+
analyse_chasers unless @chasers
|
137
|
+
if item
|
138
|
+
if block_given?
|
139
|
+
Array(@chasers[item]).each { |follower| yield follower, item }
|
140
|
+
else
|
141
|
+
Array(@chasers[item])
|
142
|
+
end
|
143
|
+
else
|
144
|
+
@enumerable.each do |item|
|
145
|
+
follower(item, &b)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Compute the page rank of the objects
|
151
|
+
# If used, the block shold return the starting weight
|
152
|
+
def page_rank damping = 0.85, &weight
|
153
|
+
weight ||= proc {|item| 1.0}
|
154
|
+
|
155
|
+
@total = 0
|
156
|
+
@rank = {}
|
157
|
+
@enumerable.each do |item|
|
158
|
+
@total +=
|
159
|
+
(@rank[item] = weight.call(item) * 1.0)
|
160
|
+
end
|
161
|
+
# Normalize:
|
162
|
+
@enumerable.each do |item|
|
163
|
+
@rank[item] /= @total
|
164
|
+
end
|
165
|
+
|
166
|
+
50.times do |iteration|
|
167
|
+
@enumerable.each do |item|
|
168
|
+
links = (precursors(item) + followers(item)).uniq
|
169
|
+
linked_rank = links.map do |l|
|
170
|
+
onward_links = (precursors(l) + followers(l)).uniq || @enumerable.size
|
171
|
+
@rank[l] / onward_links.size
|
172
|
+
end.inject(&:+) || 0
|
173
|
+
@rank[item] = (1.0-damping) + damping*linked_rank
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
@rank
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate a Relational Composition (for activefacts/composition).
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
|
+
#
|
7
|
+
require 'activefacts/vocabulary'
|
8
|
+
require 'activefacts/generate/helpers/inject'
|
9
|
+
require 'activefacts/persistence'
|
10
|
+
require 'activefacts/generate/traits/ruby'
|
11
|
+
|
12
|
+
module ActiveFacts
|
13
|
+
module Generate
|
14
|
+
# afgen --composition[=options] <file>.cql
|
15
|
+
# Options are comma or space separated:
|
16
|
+
class Composition #:nodoc:
|
17
|
+
private
|
18
|
+
include Persistence
|
19
|
+
|
20
|
+
def initialize(vocabulary, *options)
|
21
|
+
@vocabulary = vocabulary
|
22
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
23
|
+
@underscore = options.include?("underscore") ? "_" : ""
|
24
|
+
end
|
25
|
+
|
26
|
+
def puts s
|
27
|
+
@out.puts s
|
28
|
+
end
|
29
|
+
|
30
|
+
public
|
31
|
+
def generate(out = $>) #:nodoc:
|
32
|
+
@out = out
|
33
|
+
|
34
|
+
tables_emitted = {}
|
35
|
+
|
36
|
+
puts "require '#{@vocabulary.name}'"
|
37
|
+
puts "require 'activefacts/composition'"
|
38
|
+
puts "\n#{@vocabulary.name}_ER = ActiveFacts::Composition.new(#{@vocabulary.name}) do"
|
39
|
+
@vocabulary.tables.each do |table|
|
40
|
+
puts " composite :\"#{table.name.gsub(' ',@underscore)}\" do"
|
41
|
+
|
42
|
+
pk = table.identifier_columns
|
43
|
+
identity_column = pk[0] if pk[0].is_auto_assigned
|
44
|
+
|
45
|
+
fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
|
46
|
+
fk_columns = table.columns.select do |column|
|
47
|
+
column.references[0].is_simple_reference
|
48
|
+
end
|
49
|
+
|
50
|
+
columns =
|
51
|
+
table.columns.map do |column|
|
52
|
+
[column, column.references.map{|r| r.to_names }]
|
53
|
+
end.sort_by do |column, refnames|
|
54
|
+
refnames
|
55
|
+
end
|
56
|
+
previous_flattening = []
|
57
|
+
ref_prefix = []
|
58
|
+
columns.each do |column, refnames|
|
59
|
+
ref_prefix = column.references[0...previous_flattening.size]
|
60
|
+
# Pop back. Not a succinct algorithm, but easy to check
|
61
|
+
while previous_flattening.size > ref_prefix.size
|
62
|
+
previous_flattening.pop
|
63
|
+
puts ' '+' '*previous_flattening.size+"end\n"
|
64
|
+
end
|
65
|
+
while ref_prefix.size > 0 and previous_flattening != ref_prefix
|
66
|
+
previous_flattening.pop
|
67
|
+
ref_prefix.pop
|
68
|
+
puts ' '+' '*previous_flattening.size+"end\n"
|
69
|
+
end
|
70
|
+
loop do
|
71
|
+
ref = column.references[ref_prefix.size]
|
72
|
+
if ref.is_self_value
|
73
|
+
# REVISIT: I think these should be 'insert :value, :as => "XYZ"'
|
74
|
+
role_name = "value".snakecase
|
75
|
+
reading = "Intrinsic value of #{role_name}"
|
76
|
+
elsif ref.is_to_objectified_fact
|
77
|
+
# REVISIT: It's ugly to have to handle these special cases here
|
78
|
+
role_name = ref.to.name.words.snakecase
|
79
|
+
reading = ref.from_role.link_fact_type.default_reading
|
80
|
+
else
|
81
|
+
if ref.is_unary && ref.is_from_objectified_fact && ref != column.references.last
|
82
|
+
# Use the name of the objectification on the path to other absorbed fact types:
|
83
|
+
role_name = ref.to_role.fact_type.entity_type.name.words.snakecase
|
84
|
+
else
|
85
|
+
role_name = ref.to_role.preferred_role_name
|
86
|
+
end
|
87
|
+
# puts ">>>>> #{ref.inspect}: #{role_name} <<<<<<"
|
88
|
+
reading = ref.fact_type.default_reading
|
89
|
+
end
|
90
|
+
if ref == column.references.last
|
91
|
+
# REVISIT: Avoid the "as" here when the value is implied by the role_name:
|
92
|
+
puts ' '+' '*ref_prefix.size+"nest :#{role_name}, :as => \"#{column.name}\"\t\t# #{reading}"
|
93
|
+
break
|
94
|
+
else
|
95
|
+
puts ' '+' '*ref_prefix.size+"flatten :#{role_name} do\t\t# #{reading}"
|
96
|
+
ref_prefix.push ref
|
97
|
+
end
|
98
|
+
end
|
99
|
+
previous_flattening = ref_prefix
|
100
|
+
end
|
101
|
+
|
102
|
+
while previous_flattening.size > 0
|
103
|
+
previous_flattening.pop
|
104
|
+
puts ' '+' '*previous_flattening.size+"end\n"
|
105
|
+
end
|
106
|
+
puts " end\n\n"
|
107
|
+
|
108
|
+
tables_emitted[table] = true
|
109
|
+
|
110
|
+
end
|
111
|
+
puts "end\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
ActiveFacts::Registry.generator('composition', ActiveFacts::Generate::Composition)
|