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.
@@ -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)