typed_dag 1.0.1 → 2.0.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/README.md +20 -5
- data/lib/typed_dag/configuration.rb +4 -0
- data/lib/typed_dag/edge/closure_maintenance.rb +16 -2
- data/lib/typed_dag/sql.rb +1 -0
- data/lib/typed_dag/sql/add_closure.rb +36 -5
- data/lib/typed_dag/sql/delete_zero_count.rb +22 -0
- data/lib/typed_dag/sql/get_circular.rb +4 -2
- data/lib/typed_dag/sql/helper.rb +15 -1
- data/lib/typed_dag/sql/insert_closure_of_depth.rb +44 -5
- data/lib/typed_dag/sql/insert_reflexive.rb +3 -2
- data/lib/typed_dag/sql/relation_access.rb +1 -0
- data/lib/typed_dag/sql/select_closure.rb +20 -9
- data/lib/typed_dag/sql/truncate_closure.rb +23 -128
- data/lib/typed_dag/version.rb +1 -1
- data/spec/support/helpers.rb +10 -1
- data/spec/test_app/config/database.yml +1 -3
- data/spec/test_app/db/migrate/20170831093433_create_nodes_and_endges.rb +3 -4
- data/spec/test_app/db/schema.rb +7 -6
- data/spec/test_app/log/test.log +0 -0
- data/spec/typed_dag/edge_spec.rb +33 -33
- data/spec/typed_dag/node_spec.rb +43 -50
- data/spec/typed_dag/sql/add_closure_spec.rb +103 -37
- data/spec/typed_dag/sql/truncate_closure_spec.rb +93 -121
- metadata +3 -2
data/lib/typed_dag/version.rb
CHANGED
data/spec/support/helpers.rb
CHANGED
@@ -30,9 +30,18 @@ module TypedDag
|
|
30
30
|
[r.from.text,
|
31
31
|
r.to.text,
|
32
32
|
r.hierarchy,
|
33
|
-
r.invalidate
|
33
|
+
r.invalidate,
|
34
|
+
r.count]
|
34
35
|
end
|
35
36
|
end
|
37
|
+
|
38
|
+
def mysql_db?
|
39
|
+
ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
40
|
+
end
|
41
|
+
|
42
|
+
def harmonize_string(string)
|
43
|
+
string.squish.gsub('( ', '(').gsub(' )', ')')
|
44
|
+
end
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
@@ -5,12 +5,10 @@
|
|
5
5
|
# gem 'sqlite3'
|
6
6
|
#
|
7
7
|
default: &default
|
8
|
-
|
9
|
-
adapter: mysql2
|
8
|
+
adapter: postgresql
|
10
9
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
11
10
|
database: typed_dag_test
|
12
11
|
timeout: 5000
|
13
|
-
username: root
|
14
12
|
|
15
13
|
development:
|
16
14
|
<<: *default
|
@@ -10,11 +10,10 @@ class CreateNodesAndEndges < ActiveRecord::Migration[5.1]
|
|
10
10
|
|
11
11
|
t.column :hierarchy, :integer, null: false, default: 0
|
12
12
|
t.column :invalidate, :integer, null: false, default: 0
|
13
|
-
t.column :mention, :integer, null: false, default: 0
|
14
13
|
|
15
|
-
t.
|
16
|
-
|
17
|
-
t.index :
|
14
|
+
t.column :count, :integer, null: false, default: 1
|
15
|
+
|
16
|
+
t.index %i(ancestor_id descendant_id hierarchy invalidate), unique: true, name: 'unique_constraint'
|
18
17
|
end
|
19
18
|
|
20
19
|
add_foreign_key :relations, :messages, column: :ancestor_id
|
data/spec/test_app/db/schema.rb
CHANGED
@@ -12,21 +12,22 @@
|
|
12
12
|
|
13
13
|
ActiveRecord::Schema.define(version: 20170831093433) do
|
14
14
|
|
15
|
-
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "plpgsql"
|
17
|
+
|
18
|
+
create_table "messages", force: :cascade do |t|
|
16
19
|
t.string "text"
|
17
20
|
end
|
18
21
|
|
19
|
-
create_table "relations", force: :cascade
|
22
|
+
create_table "relations", force: :cascade do |t|
|
20
23
|
t.bigint "ancestor_id", null: false
|
21
24
|
t.bigint "descendant_id", null: false
|
22
25
|
t.integer "hierarchy", default: 0, null: false
|
23
26
|
t.integer "invalidate", default: 0, null: false
|
24
|
-
t.integer "
|
27
|
+
t.integer "count", default: 0, null: false
|
28
|
+
t.index ["ancestor_id", "descendant_id", "hierarchy", "invalidate"], name: "unique_constraint", unique: true
|
25
29
|
t.index ["ancestor_id"], name: "index_relations_on_ancestor_id"
|
26
30
|
t.index ["descendant_id"], name: "index_relations_on_descendant_id"
|
27
|
-
t.index ["hierarchy"], name: "index_relations_on_hierarchy"
|
28
|
-
t.index ["invalidate"], name: "index_relations_on_invalidate"
|
29
|
-
t.index ["mention"], name: "index_relations_on_mention"
|
30
31
|
end
|
31
32
|
|
32
33
|
add_foreign_key "relations", "messages", column: "ancestor_id"
|
data/spec/test_app/log/test.log
CHANGED
Binary file
|
data/spec/typed_dag/edge_spec.rb
CHANGED
@@ -347,15 +347,15 @@ RSpec.describe 'Edge' do
|
|
347
347
|
it 'expects the transitive relations to be updated' do
|
348
348
|
attribute_array = to_attribute_array Relation.non_reflexive
|
349
349
|
expect(attribute_array)
|
350
|
-
.to match_array([['A', 'B', 0, 1],
|
351
|
-
['A', 'C', 1, 1],
|
352
|
-
['A', 'D', 1, 2],
|
353
|
-
['A', 'E', 2, 1],
|
354
|
-
['B', 'C', 1, 0],
|
355
|
-
['B', 'D', 1, 1],
|
356
|
-
['B', 'E', 2, 0],
|
357
|
-
['C', 'D', 0, 1],
|
358
|
-
['C', 'E', 1, 0]])
|
350
|
+
.to match_array([['A', 'B', 0, 1, 1],
|
351
|
+
['A', 'C', 1, 1, 1],
|
352
|
+
['A', 'D', 1, 2, 1],
|
353
|
+
['A', 'E', 2, 1, 1],
|
354
|
+
['B', 'C', 1, 0, 1],
|
355
|
+
['B', 'D', 1, 1, 1],
|
356
|
+
['B', 'E', 2, 0, 1],
|
357
|
+
['C', 'D', 0, 1, 1],
|
358
|
+
['C', 'E', 1, 0, 1]])
|
359
359
|
end
|
360
360
|
end
|
361
361
|
end
|
@@ -415,15 +415,15 @@ RSpec.describe 'Edge' do
|
|
415
415
|
it 'expects the transitive relations to be updated' do
|
416
416
|
attribute_array = to_attribute_array Relation.non_reflexive
|
417
417
|
expect(attribute_array)
|
418
|
-
.to match_array([['F', 'B', 0, 1],
|
419
|
-
['F', 'C', 0, 2],
|
420
|
-
['F', 'D', 0, 3],
|
421
|
-
['F', 'E', 1, 2],
|
422
|
-
['B', 'C', 0, 1],
|
423
|
-
['B', 'D', 0, 2],
|
424
|
-
['B', 'E', 1, 1],
|
425
|
-
['C', 'D', 0, 1],
|
426
|
-
['C', 'E', 1, 0]])
|
418
|
+
.to match_array([['F', 'B', 0, 1, 1],
|
419
|
+
['F', 'C', 0, 2, 1],
|
420
|
+
['F', 'D', 0, 3, 1],
|
421
|
+
['F', 'E', 1, 2, 1],
|
422
|
+
['B', 'C', 0, 1, 1],
|
423
|
+
['B', 'D', 0, 2, 1],
|
424
|
+
['B', 'E', 1, 1, 1],
|
425
|
+
['C', 'D', 0, 1, 1],
|
426
|
+
['C', 'E', 1, 0, 1]])
|
427
427
|
end
|
428
428
|
end
|
429
429
|
end
|
@@ -483,15 +483,15 @@ RSpec.describe 'Edge' do
|
|
483
483
|
it 'expects the transitive relations to be updated' do
|
484
484
|
attribute_array = to_attribute_array Relation.non_reflexive
|
485
485
|
expect(attribute_array)
|
486
|
-
.to match_array([['A', 'B', 0, 1],
|
487
|
-
['A', 'C', 0, 2],
|
488
|
-
['A', 'F', 0, 3],
|
489
|
-
['A', 'E', 1, 2],
|
490
|
-
['B', 'C', 0, 1],
|
491
|
-
['B', 'F', 0, 2],
|
492
|
-
['B', 'E', 1, 1],
|
493
|
-
['C', 'F', 0, 1],
|
494
|
-
['C', 'E', 1, 0]])
|
486
|
+
.to match_array([['A', 'B', 0, 1, 1],
|
487
|
+
['A', 'C', 0, 2, 1],
|
488
|
+
['A', 'F', 0, 3, 1],
|
489
|
+
['A', 'E', 1, 2, 1],
|
490
|
+
['B', 'C', 0, 1, 1],
|
491
|
+
['B', 'F', 0, 2, 1],
|
492
|
+
['B', 'E', 1, 1, 1],
|
493
|
+
['C', 'F', 0, 1, 1],
|
494
|
+
['C', 'E', 1, 0, 1]])
|
495
495
|
end
|
496
496
|
end
|
497
497
|
end
|
@@ -551,12 +551,12 @@ RSpec.describe 'Edge' do
|
|
551
551
|
it 'expects the transitive relations to be updated' do
|
552
552
|
attribute_array = to_attribute_array Relation.non_reflexive
|
553
553
|
expect(attribute_array)
|
554
|
-
.to match_array([['A', 'B', 0, 1],
|
555
|
-
['F', 'C', 0, 1],
|
556
|
-
['C', 'D', 0, 1],
|
557
|
-
['C', 'E', 1, 0],
|
558
|
-
['F', 'D', 0, 2],
|
559
|
-
['F', 'E', 1, 1]])
|
554
|
+
.to match_array([['A', 'B', 0, 1, 1],
|
555
|
+
['F', 'C', 0, 1, 1],
|
556
|
+
['C', 'D', 0, 1, 1],
|
557
|
+
['C', 'E', 1, 0, 1],
|
558
|
+
['F', 'D', 0, 2, 1],
|
559
|
+
['F', 'E', 1, 1, 1]])
|
560
560
|
end
|
561
561
|
end
|
562
562
|
end
|
data/spec/typed_dag/node_spec.rb
CHANGED
@@ -1953,6 +1953,7 @@ RSpec.describe TypedDag::Node, 'included in Message' do
|
|
1953
1953
|
end
|
1954
1954
|
|
1955
1955
|
describe '#rebuild_dag!' do
|
1956
|
+
|
1956
1957
|
description = <<-'WITH'
|
1957
1958
|
|
1958
1959
|
DAG (messed up transitive closures):
|
@@ -1998,44 +1999,32 @@ RSpec.describe TypedDag::Node, 'included in Message' do
|
|
1998
1999
|
Message.rebuild_dag!
|
1999
2000
|
end
|
2000
2001
|
|
2001
|
-
it '
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
expect(a.all_invalidates_of_depth(3))
|
2028
|
-
.to match_array([g, f])
|
2029
|
-
end
|
2030
|
-
|
2031
|
-
it '#descendants_of_depth(4) for A is empty' do
|
2032
|
-
expect(a.descendants_of_depth(4))
|
2033
|
-
.to be_empty
|
2034
|
-
end
|
2035
|
-
|
2036
|
-
it '#all_invalidates_of_depth(4) for A is G' do
|
2037
|
-
expect(a.all_invalidates_of_depth(4))
|
2038
|
-
.to match_array([g])
|
2002
|
+
it 'rebuilds the closure for A correctly' do
|
2003
|
+
attribute_array = to_attribute_array a.relations_to
|
2004
|
+
expect(attribute_array)
|
2005
|
+
.to match_array([['A', 'A', 0, 0, 1],
|
2006
|
+
['A', 'B', 0, 1, 1],
|
2007
|
+
['A', 'C', 0, 1, 1],
|
2008
|
+
['A', 'C', 0, 2, 2],
|
2009
|
+
['A', 'D', 0, 1, 1],
|
2010
|
+
['A', 'E', 0, 2, 1],
|
2011
|
+
['A', 'F', 0, 2, 1],
|
2012
|
+
['A', 'F', 0, 3, 3],
|
2013
|
+
['A', 'G', 0, 3, 1],
|
2014
|
+
['A', 'G', 0, 4, 3],
|
2015
|
+
['A', 'G', 3, 0, 1],
|
2016
|
+
['A', 'H', 1, 0, 1],
|
2017
|
+
['A', 'I', 2, 0, 1]])
|
2018
|
+
end
|
2019
|
+
|
2020
|
+
it 'rebuilds the closure for B correctly' do
|
2021
|
+
attribute_array = to_attribute_array b.relations_to
|
2022
|
+
expect(attribute_array)
|
2023
|
+
.to match_array([['B', 'B', 0, 0, 1],
|
2024
|
+
['B', 'C', 0, 1, 1],
|
2025
|
+
['B', 'E', 0, 1, 1],
|
2026
|
+
['B', 'F', 0, 2, 2],
|
2027
|
+
['B', 'G', 0, 3, 2]])
|
2039
2028
|
end
|
2040
2029
|
|
2041
2030
|
it 'rebuilds all reflexive relations' do
|
@@ -2096,32 +2085,36 @@ RSpec.describe TypedDag::Node, 'included in Message' do
|
|
2096
2085
|
description = <<-'WITH'
|
2097
2086
|
|
2098
2087
|
DAG (invalid) before:
|
2099
|
-
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2088
|
+
C+-h--B
|
2089
|
+
|+ +
|
2090
|
+
| \ |
|
2091
|
+
h i h
|
2092
|
+
| \ |
|
2093
|
+
+ \|
|
2094
|
+
D--h-+A
|
2104
2095
|
WITH
|
2105
2096
|
|
2106
2097
|
context description do
|
2107
2098
|
let!(:a) { Message.create text: 'A' }
|
2108
2099
|
let!(:b) { Message.create text: 'B', parent: a }
|
2109
2100
|
let!(:c) { Message.create text: 'C', parent: b }
|
2110
|
-
let!(:
|
2101
|
+
let!(:d) { Message.create text: 'D', parent: c }
|
2102
|
+
let!(:invalid_relation1) do
|
2111
2103
|
Relation
|
2112
|
-
.new(from:
|
2113
|
-
to:
|
2104
|
+
.new(from: a,
|
2105
|
+
to: c,
|
2114
2106
|
invalidate: 1)
|
2115
2107
|
.save(validate: false)
|
2116
|
-
|
2108
|
+
end
|
2109
|
+
let!(:invalid_relation2) do
|
2117
2110
|
Relation
|
2118
|
-
.new(from:
|
2111
|
+
.new(from: d,
|
2119
2112
|
to: a,
|
2120
|
-
|
2113
|
+
hierarchy: 1)
|
2121
2114
|
.save(validate: false)
|
2122
2115
|
end
|
2123
2116
|
|
2124
|
-
it 'throws an error if more
|
2117
|
+
it 'throws an error if more attempts than specified are made' do
|
2125
2118
|
expect { Message.rebuild_dag!(1) }
|
2126
2119
|
.to raise_error(TypedDag::RebuildDag::AttemptsExceededError)
|
2127
2120
|
end
|
@@ -1,47 +1,113 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe TypedDag::Sql::AddClosure do
|
4
|
+
include TypedDag::Specs::Helpers
|
5
|
+
|
4
6
|
describe '.sql' do
|
5
7
|
let(:relation) { Relation.new id: 11, ancestor_id: 4, descendant_id: 6, invalidate: 1 }
|
6
8
|
|
7
|
-
def harmonize_string(string)
|
8
|
-
string.squish.gsub('( ', '(').gsub(' )', ')').gsub(' , ', ', ')
|
9
|
-
end
|
10
|
-
|
11
9
|
it 'produces the correct sql' do
|
12
|
-
expected_sql =
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
10
|
+
expected_sql = if mysql_db?
|
11
|
+
<<-SQL
|
12
|
+
INSERT INTO
|
13
|
+
|
14
|
+
relations
|
15
|
+
|
16
|
+
(ancestor_id,
|
17
|
+
descendant_id,
|
18
|
+
hierarchy,
|
19
|
+
invalidate,
|
20
|
+
count)
|
21
|
+
SELECT
|
22
|
+
ancestor_id,
|
23
|
+
descendant_id,
|
24
|
+
hierarchy,
|
25
|
+
invalidate,
|
26
|
+
SUM(count) AS count
|
27
|
+
FROM
|
28
|
+
(SELECT
|
29
|
+
r1.ancestor_id,
|
30
|
+
r2.descendant_id,
|
31
|
+
CASE
|
32
|
+
WHEN r1.descendant_id = r2.ancestor_id AND (r1.hierarchy > 0 OR r2.hierarchy > 0)
|
33
|
+
THEN r1.hierarchy + r2.hierarchy
|
34
|
+
WHEN r1.descendant_id != r2.ancestor_id
|
35
|
+
THEN r1.hierarchy + r2.hierarchy + 0
|
36
|
+
ELSE 0
|
37
|
+
END AS hierarchy,
|
38
|
+
CASE
|
39
|
+
WHEN r1.descendant_id = r2.ancestor_id AND (r1.invalidate > 0 OR r2.invalidate > 0)
|
40
|
+
THEN r1.invalidate + r2.invalidate
|
41
|
+
WHEN r1.descendant_id != r2.ancestor_id
|
42
|
+
THEN r1.invalidate + r2.invalidate + 1
|
43
|
+
ELSE 0
|
44
|
+
END AS invalidate,
|
45
|
+
r1.count * r2.count AS count
|
46
|
+
FROM
|
47
|
+
relations r1
|
48
|
+
JOIN
|
49
|
+
relations r2
|
50
|
+
ON
|
51
|
+
(r1.descendant_id = 4 AND r2.ancestor_id = 6 AND NOT (r1.ancestor_id = 4 AND r2.descendant_id = 6))) unique_rows
|
52
|
+
GROUP BY
|
53
|
+
ancestor_id,
|
54
|
+
descendant_id,
|
55
|
+
hierarchy,
|
56
|
+
invalidate
|
57
|
+
ON DUPLICATE KEY
|
58
|
+
UPDATE count = relations.count + VALUES(count)
|
59
|
+
SQL
|
60
|
+
else
|
61
|
+
<<-SQL
|
62
|
+
INSERT INTO
|
63
|
+
|
64
|
+
relations
|
65
|
+
|
66
|
+
(ancestor_id,
|
67
|
+
descendant_id,
|
68
|
+
hierarchy,
|
69
|
+
invalidate,
|
70
|
+
count)
|
71
|
+
SELECT
|
72
|
+
ancestor_id,
|
73
|
+
descendant_id,
|
74
|
+
hierarchy,
|
75
|
+
invalidate,
|
76
|
+
SUM(count) AS count
|
77
|
+
FROM
|
78
|
+
(SELECT
|
79
|
+
r1.ancestor_id,
|
80
|
+
r2.descendant_id,
|
81
|
+
CASE
|
82
|
+
WHEN r1.descendant_id = r2.ancestor_id AND (r1.hierarchy > 0 OR r2.hierarchy > 0)
|
83
|
+
THEN r1.hierarchy + r2.hierarchy
|
84
|
+
WHEN r1.descendant_id != r2.ancestor_id
|
85
|
+
THEN r1.hierarchy + r2.hierarchy + 0
|
86
|
+
ELSE 0
|
87
|
+
END AS hierarchy,
|
88
|
+
CASE
|
89
|
+
WHEN r1.descendant_id = r2.ancestor_id AND (r1.invalidate > 0 OR r2.invalidate > 0)
|
90
|
+
THEN r1.invalidate + r2.invalidate
|
91
|
+
WHEN r1.descendant_id != r2.ancestor_id
|
92
|
+
THEN r1.invalidate + r2.invalidate + 1
|
93
|
+
ELSE 0
|
94
|
+
END AS invalidate,
|
95
|
+
r1.count * r2.count AS count
|
96
|
+
FROM
|
97
|
+
relations r1
|
98
|
+
JOIN
|
99
|
+
relations r2
|
100
|
+
ON
|
101
|
+
(r1.descendant_id = 4 AND r2.ancestor_id = 6 AND NOT (r1.ancestor_id = 4 AND r2.descendant_id = 6))) unique_rows
|
102
|
+
GROUP BY
|
103
|
+
ancestor_id,
|
104
|
+
descendant_id,
|
105
|
+
hierarchy,
|
106
|
+
invalidate
|
107
|
+
ON CONFLICT (ancestor_id, descendant_id, hierarchy, invalidate)
|
108
|
+
DO UPDATE SET count = relations.count + EXCLUDED.count
|
109
|
+
SQL
|
110
|
+
end
|
45
111
|
|
46
112
|
expect(harmonize_string(described_class.sql(relation)))
|
47
113
|
.to eql harmonize_string(expected_sql)
|