typed_dag 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module TypedDag
2
- VERSION = '1.0.1'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -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
- #adapter: postgresql
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.index :hierarchy
16
- t.index :invalidate
17
- t.index :mention
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
@@ -12,21 +12,22 @@
12
12
 
13
13
  ActiveRecord::Schema.define(version: 20170831093433) do
14
14
 
15
- create_table "messages", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
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, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
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 "mention", default: 0, null: false
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"
Binary file
@@ -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
@@ -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 '#descendants_of_depth(1) for A is H' do
2002
- expect(a.descendants_of_depth(1))
2003
- .to match_array([h])
2004
- end
2005
-
2006
- it '#all_invalidates_of_depth(1) for A is B, C, D' do
2007
- expect(a.all_invalidates_of_depth(1))
2008
- .to match_array([b, c, d])
2009
- end
2010
-
2011
- it '#descendants_of_depth(2) for A is H' do
2012
- expect(a.descendants_of_depth(2))
2013
- .to match_array([i])
2014
- end
2015
-
2016
- it '#all_invalidates_of_depth(2) for A is C, E, F' do
2017
- expect(a.all_invalidates_of_depth(2))
2018
- .to match_array([c, e, f])
2019
- end
2020
-
2021
- it '#descendants_of_depth(3) for A is G' do
2022
- expect(a.descendants_of_depth(3))
2023
- .to match_array([g])
2024
- end
2025
-
2026
- it '#all_invalidates_of_depth(3) for A is G and F' do
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
- --+A
2100
- | + \
2101
- i i h
2102
- |/ +
2103
- C+--h---B
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!(:invalid_relation) do
2101
+ let!(:d) { Message.create text: 'D', parent: c }
2102
+ let!(:invalid_relation1) do
2111
2103
  Relation
2112
- .new(from: c,
2113
- to: a,
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: c,
2111
+ .new(from: d,
2119
2112
  to: a,
2120
- invalidate: 1)
2113
+ hierarchy: 1)
2121
2114
  .save(validate: false)
2122
2115
  end
2123
2116
 
2124
- it 'throws an error if more attepts than specified are made' do
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 = <<-SQL
13
- INSERT INTO
14
-
15
- relations
16
-
17
- (ancestor_id,
18
- descendant_id,
19
- hierarchy, invalidate)
20
- SELECT
21
- r1.ancestor_id,
22
- r2.descendant_id,
23
- CASE
24
- WHEN r1.descendant_id = r2.ancestor_id AND (r1.hierarchy > 0 OR r2.hierarchy > 0)
25
- THEN r1.hierarchy + r2.hierarchy
26
- WHEN r1.descendant_id != r2.ancestor_id
27
- THEN r1.hierarchy + r2.hierarchy + 0
28
- ELSE 0
29
- END AS hierarchy,
30
- CASE
31
- WHEN r1.descendant_id = r2.ancestor_id AND (r1.invalidate > 0 OR r2.invalidate > 0)
32
- THEN r1.invalidate + r2.invalidate
33
- WHEN r1.descendant_id != r2.ancestor_id
34
- THEN r1.invalidate + r2.invalidate + 1
35
- ELSE 0
36
- END AS invalidate
37
-
38
- FROM
39
- relations r1
40
- JOIN
41
- relations r2
42
- ON
43
- (r1.descendant_id = 4 AND r2.ancestor_id = 6 AND NOT (r1.ancestor_id = 4 AND r2.descendant_id = 6))
44
- SQL
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)