activefacts 1.3.0 → 1.5.0
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.
- 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)
|