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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7e31c0c6a123f535ad41b59c8aabaac96129a70
|
4
|
+
data.tar.gz: 78ee741a3bf16ea7014f2cf69a8a3f5903c9fab1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7941c61304bd469a8bff392405f09719f7246e6817e991cf835f7435d0570d8b832b37edaf87186ab359b57a20a0f5a5e949ffb65f87149b6745b4ab21be3b3c
|
7
|
+
data.tar.gz: 206c14332f5e58353063cf45ac0e8d279f632f017a14bbd1c96a0a2f4ab8e9cf3424f33cc1b2378c13eaf9363259caf2a8b26e920f37ec4a7eea412e24f2e383
|
data/README.md
CHANGED
@@ -35,6 +35,10 @@ Again using the forum application as an example, one would have messages in a hi
|
|
35
35
|
|
36
36
|
Please note that the name of the scopes (e.g. `children`, `hierarchy_roots` and `referenced`) and constants (`Message`) have to be configured.
|
37
37
|
|
38
|
+
## Requirements
|
39
|
+
|
40
|
+
* Rails >= 5.0
|
41
|
+
* MySQL or PostgreSQL (>= 9.5) as `UPSERT` statements are used
|
38
42
|
|
39
43
|
## Installation
|
40
44
|
|
@@ -62,10 +66,12 @@ To avoid having to configure TypedDag twice, in the node and in the edge AR mode
|
|
62
66
|
|
63
67
|
```
|
64
68
|
# /config/initializers/typed_dag.rb
|
69
|
+
# configuration for Relation/Message
|
65
70
|
TypedDag::Configuration.set edge_class_name: 'Relation',
|
66
71
|
node_class_name: 'Message',
|
67
72
|
from_column: 'ancestor_id',
|
68
73
|
to_column: 'descendant_id',
|
74
|
+
count_column: 'amount',
|
69
75
|
types: { hierarchy: { from: { name: :parent, limit: 1 },
|
70
76
|
to: :children,
|
71
77
|
all_from: :ancestors,
|
@@ -75,6 +81,8 @@ To avoid having to configure TypedDag twice, in the node and in the edge AR mode
|
|
75
81
|
all_from: :all_invalidated_by,
|
76
82
|
all_to: :all_invalidates } }
|
77
83
|
|
84
|
+
|
85
|
+
# unrelated configuration for Edge/Node
|
78
86
|
TypedDag::Configuration.set edge_class_name: 'Edge'
|
79
87
|
node_class_name: 'Node',
|
80
88
|
types: { edge: { from: :edges_from,
|
@@ -93,6 +101,7 @@ The following options exist:
|
|
93
101
|
* `node_class_name`: The name of the AR model whose instances serve as the nodes of the dag
|
94
102
|
* `from_column` (default `from_id`): The name of the column in the edges AR model that refer to the node the edge starts from
|
95
103
|
* `to_column` (default `to_id`): The name of the column in the edges AR model that refer to the node the edge ends in
|
104
|
+
* `count_column` (default `count`): The name of the column in the edges AR model that keeps track of the number of identical edges between from and to
|
96
105
|
* `types`: The hash of type configurations. The key of each configuration will need to be present as a column in the edge's DB table.
|
97
106
|
* `from`: The AR association's name for nodes having a relation which end in the current node, have the type specified by the key and are not transitive (have only one hop). Only for `from` can one specify a limit to the number of relations a node can have. Doing this turns the DAG into a tree which is usefull for hierarchies. If a limit needs to be specified, the configuration has to be provided as `{ name: [association's name], limit: 1 }`. If no limit is given, the association's name can be provided as a symbol.
|
98
107
|
* `to`: The AR association's name for nodes having a relation which start from the current node, have the type specified by the key and are not transitive (have only one hop)
|
@@ -156,7 +165,8 @@ The edge's table needs to be created containing at least the following columns (
|
|
156
165
|
|
157
166
|
* `from_id`: A reference to the node the edge starts from.
|
158
167
|
* `to_id`: A reference to the node the edge ends in.
|
159
|
-
* `
|
168
|
+
* `count`: The counter column for the number of similar (transitive) edges between to and from.
|
169
|
+
* `[key]`: A column of type integer for every type the dag is to support.
|
160
170
|
|
161
171
|
A migration to create such a table could look like this:
|
162
172
|
|
@@ -166,6 +176,8 @@ A migration to create such a table could look like this:
|
|
166
176
|
t.references :from, null: false
|
167
177
|
t.references :to, null: false
|
168
178
|
|
179
|
+
t.column :count, :integer, null: false, default: 0
|
180
|
+
|
169
181
|
t.column :hierarchy, :integer, null: false, default: 0
|
170
182
|
t.column :reference, :integer, null: false, default: 0
|
171
183
|
end
|
@@ -173,17 +185,20 @@ A migration to create such a table could look like this:
|
|
173
185
|
add_foreign_key :edges, :nodes, column: :from_id
|
174
186
|
add_foreign_key :edges, :nodes, column: :to_id
|
175
187
|
|
188
|
+
# give the index a custom name to avoid running into length limitation when having a couple of columns
|
189
|
+
# in the index
|
176
190
|
add_index :edges, [:hierarchy, :reference], name: `index_on_type_columns`
|
191
|
+
add_index :edges, :count, where: 'count = 0'
|
177
192
|
end
|
178
193
|
```
|
179
194
|
|
180
|
-
|
195
|
+
The table can also have additional columns. They will not interfere with TypedDag.
|
181
196
|
|
182
|
-
Which indices to use will depend on the data added but having an index over all the type columns is a good start.
|
197
|
+
Which indices to use will depend on the data added but having an index over all the type columns is a good start. A partial index on count speeds up deleting edges while also being very lightweight to maintain.
|
183
198
|
|
184
|
-
There are no requirements
|
199
|
+
There are no requirements on the node's table.
|
185
200
|
|
186
|
-
When migrating from
|
201
|
+
When migrating from a different library, the details of course depend on the library used. If it is one of the many having a `parent_id` column on the node table, one would first have to create the edge table as outlined above and then add a SQL statement like this:
|
187
202
|
|
188
203
|
```
|
189
204
|
ActiveRecord::Base.connection.execute <<-SQL
|
@@ -3,12 +3,17 @@ module TypedDag::Edge
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
+
before_create :set_count
|
6
7
|
after_create :add_closures
|
7
8
|
after_update :alter_closure
|
8
9
|
after_destroy :truncate_closures
|
9
10
|
|
10
11
|
private
|
11
12
|
|
13
|
+
def set_count
|
14
|
+
send("#{_dag_options.count_column}=", 1) if send(_dag_options.count_column).zero?
|
15
|
+
end
|
16
|
+
|
12
17
|
def add_closures
|
13
18
|
return unless direct?
|
14
19
|
|
@@ -20,7 +25,7 @@ module TypedDag::Edge
|
|
20
25
|
# However, #persisted? will be false for destroyed records.
|
21
26
|
return unless direct? && !new_record?
|
22
27
|
|
23
|
-
|
28
|
+
update_and_delete_closure(self)
|
24
29
|
end
|
25
30
|
|
26
31
|
def truncate_closures_with_former_values
|
@@ -35,7 +40,7 @@ module TypedDag::Edge
|
|
35
40
|
|
36
41
|
former_values_relation.attributes = changes
|
37
42
|
|
38
|
-
|
43
|
+
update_and_delete_closure(former_values_relation)
|
39
44
|
end
|
40
45
|
|
41
46
|
def alter_closure
|
@@ -45,6 +50,11 @@ module TypedDag::Edge
|
|
45
50
|
add_closures
|
46
51
|
end
|
47
52
|
|
53
|
+
def update_and_delete_closure(relation)
|
54
|
+
self.class.connection.execute truncate_dag_closure_sql(relation)
|
55
|
+
self.class.connection.execute delete_zero_count_sql(relation)
|
56
|
+
end
|
57
|
+
|
48
58
|
def add_dag_closure_sql
|
49
59
|
TypedDag::Sql::AddClosure.sql(self)
|
50
60
|
end
|
@@ -53,6 +63,10 @@ module TypedDag::Edge
|
|
53
63
|
TypedDag::Sql::TruncateClosure.sql(relation)
|
54
64
|
end
|
55
65
|
|
66
|
+
def delete_zero_count_sql(relation)
|
67
|
+
TypedDag::Sql::DeleteZeroCount.sql(relation)
|
68
|
+
end
|
69
|
+
|
56
70
|
def from_id_value
|
57
71
|
send(_dag_options.from_column)
|
58
72
|
end
|
data/lib/typed_dag/sql.rb
CHANGED
@@ -15,18 +15,49 @@ module TypedDag::Sql::AddClosure
|
|
15
15
|
|
16
16
|
def sql
|
17
17
|
<<-SQL
|
18
|
-
|
19
|
-
|
20
|
-
#{to_column},
|
21
|
-
#{type_select_list})
|
22
|
-
#{closure_select}
|
18
|
+
#{insert_sql}
|
19
|
+
#{on_duplicate}
|
23
20
|
SQL
|
24
21
|
end
|
25
22
|
|
26
23
|
private
|
27
24
|
|
25
|
+
def on_duplicate
|
26
|
+
if helper.mysql_db?
|
27
|
+
on_duplicate_mysql
|
28
|
+
else
|
29
|
+
on_duplicate_postgresql
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_duplicate_mysql
|
34
|
+
<<-SQL
|
35
|
+
ON DUPLICATE KEY
|
36
|
+
UPDATE #{count_column} = #{table_name}.#{count_column} + VALUES(#{count_column})
|
37
|
+
SQL
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_duplicate_postgresql
|
41
|
+
<<-SQL
|
42
|
+
ON CONFLICT (#{column_list})
|
43
|
+
DO UPDATE SET #{count_column} = #{table_name}.#{count_column} + EXCLUDED.#{count_column}
|
44
|
+
SQL
|
45
|
+
end
|
46
|
+
|
47
|
+
def insert_sql
|
48
|
+
<<-SQL
|
49
|
+
INSERT INTO #{table_name}
|
50
|
+
(#{column_list}, #{count_column})
|
51
|
+
#{closure_select}
|
52
|
+
SQL
|
53
|
+
end
|
54
|
+
|
28
55
|
def closure_select
|
29
56
|
TypedDag::Sql::SelectClosure.sql(relation)
|
30
57
|
end
|
58
|
+
|
59
|
+
def column_list
|
60
|
+
"#{from_column}, #{to_column}, #{type_select_list}"
|
61
|
+
end
|
31
62
|
end
|
32
63
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'typed_dag/sql/helper'
|
2
|
+
|
3
|
+
module TypedDag::Sql::DeleteZeroCount
|
4
|
+
def self.sql(relation)
|
5
|
+
Sql.new(relation).sql
|
6
|
+
end
|
7
|
+
|
8
|
+
class Sql
|
9
|
+
include TypedDag::Sql::RelationAccess
|
10
|
+
|
11
|
+
def initialize(relation)
|
12
|
+
self.relation = relation
|
13
|
+
end
|
14
|
+
|
15
|
+
def sql
|
16
|
+
<<-SQL
|
17
|
+
DELETE FROM #{table_name}
|
18
|
+
WHERE #{count_column} = 0
|
19
|
+
SQL
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -13,8 +13,10 @@ module TypedDag::Sql::GetCircular
|
|
13
13
|
def sql(depth)
|
14
14
|
<<-SQL
|
15
15
|
SELECT
|
16
|
-
r1.#{helper.from_column},
|
17
|
-
r1.#{helper.to_column}
|
16
|
+
r1.#{helper.from_column} AS r1_from_column,
|
17
|
+
r1.#{helper.to_column} AS r1_to_column,
|
18
|
+
r2.#{helper.from_column} AS r2_from_column,
|
19
|
+
r2.#{helper.to_column} AS r2_to_column
|
18
20
|
FROM #{helper.table_name} r1
|
19
21
|
JOIN #{helper.table_name} r2
|
20
22
|
ON #{join_condition(depth)}
|
data/lib/typed_dag/sql/helper.rb
CHANGED
@@ -23,13 +23,23 @@ class TypedDag::Sql::Helper
|
|
23
23
|
configuration.type_columns
|
24
24
|
end
|
25
25
|
|
26
|
+
def count_column
|
27
|
+
configuration.count_column
|
28
|
+
end
|
29
|
+
|
26
30
|
def type_select_list
|
27
31
|
type_columns.join(', ')
|
28
32
|
end
|
29
33
|
|
30
34
|
def type_select_summed_columns(prefix1, prefix2)
|
31
35
|
type_columns
|
32
|
-
.map { |column| "#{prefix1}.#{column} + #{prefix2}.#{column}" }
|
36
|
+
.map { |column| "#{prefix1}.#{column} + #{prefix2}.#{column} " }
|
37
|
+
.join(', ')
|
38
|
+
end
|
39
|
+
|
40
|
+
def type_select_summed_columns_aliased(prefix1, prefix2)
|
41
|
+
type_columns
|
42
|
+
.map { |column| "(#{prefix1}.#{column} + #{prefix2}.#{column}) #{column}" }
|
33
43
|
.join(', ')
|
34
44
|
end
|
35
45
|
|
@@ -45,6 +55,10 @@ class TypedDag::Sql::Helper
|
|
45
55
|
type_columns.map { |column| "#{prefix}#{column} = 1" }.join(' XOR ')
|
46
56
|
end
|
47
57
|
|
58
|
+
def mysql_db?
|
59
|
+
ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
60
|
+
end
|
61
|
+
|
48
62
|
private
|
49
63
|
|
50
64
|
attr_accessor :configuration
|
@@ -11,19 +11,50 @@ module TypedDag::Sql::InsertClosureOfDepth
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def sql(depth)
|
14
|
+
if helper.mysql_db?
|
15
|
+
sql_mysql(depth)
|
16
|
+
else
|
17
|
+
sql_postgresql(depth)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def sql_mysql(depth)
|
24
|
+
<<-SQL
|
25
|
+
#{insert_sql(depth)}
|
26
|
+
ON DUPLICATE KEY
|
27
|
+
UPDATE #{helper.table_name}.#{helper.count_column} = #{helper.table_name}.#{helper.count_column} + VALUES(#{helper.count_column})
|
28
|
+
SQL
|
29
|
+
end
|
30
|
+
|
31
|
+
def sql_postgresql(depth)
|
32
|
+
<<-SQL
|
33
|
+
#{insert_sql(depth)}
|
34
|
+
ON CONFLICT (#{insert_list})
|
35
|
+
DO UPDATE SET #{helper.count_column} = #{helper.table_name}.#{helper.count_column} + EXCLUDED.#{helper.count_column}
|
36
|
+
SQL
|
37
|
+
end
|
38
|
+
|
39
|
+
def insert_sql(depth)
|
14
40
|
<<-SQL
|
15
41
|
INSERT INTO #{helper.table_name}
|
16
|
-
(#{insert_list})
|
17
|
-
SELECT
|
18
|
-
#{
|
42
|
+
(#{insert_list}, #{helper.count_column})
|
43
|
+
SELECT #{insert_list}, #{helper.count_column} FROM
|
44
|
+
(#{sum_select(depth)}) to_insert
|
45
|
+
SQL
|
46
|
+
end
|
47
|
+
|
48
|
+
def sum_select(depth)
|
49
|
+
<<-SQL
|
50
|
+
SELECT #{select_list}, SUM(r1.#{helper.count_column} * r2.#{helper.count_column}) AS #{helper.count_column}
|
19
51
|
FROM #{helper.table_name} r1
|
20
52
|
JOIN #{helper.table_name} r2
|
21
53
|
ON #{join_condition(depth)}
|
54
|
+
GROUP BY #{group_list}
|
22
55
|
SQL
|
23
56
|
end
|
24
57
|
|
25
|
-
private
|
26
|
-
|
27
58
|
def insert_list
|
28
59
|
[helper.from_column,
|
29
60
|
helper.to_column,
|
@@ -31,6 +62,14 @@ module TypedDag::Sql::InsertClosureOfDepth
|
|
31
62
|
end
|
32
63
|
|
33
64
|
def select_list
|
65
|
+
<<-SQL
|
66
|
+
r1.#{helper.from_column},
|
67
|
+
r2.#{helper.to_column},
|
68
|
+
#{helper.type_select_summed_columns_aliased('r1', 'r2')}
|
69
|
+
SQL
|
70
|
+
end
|
71
|
+
|
72
|
+
def group_list
|
34
73
|
<<-SQL
|
35
74
|
r1.#{helper.from_column},
|
36
75
|
r2.#{helper.to_column},
|
@@ -14,8 +14,9 @@ module TypedDag::Sql::InsertReflexive
|
|
14
14
|
<<-SQL
|
15
15
|
INSERT INTO #{helper.table_name}
|
16
16
|
(#{helper.from_column},
|
17
|
-
#{helper.to_column}
|
18
|
-
|
17
|
+
#{helper.to_column},
|
18
|
+
#{helper.count_column})
|
19
|
+
SELECT id, id, 1
|
19
20
|
FROM #{helper.node_table_name}
|
20
21
|
SQL
|
21
22
|
end
|
@@ -15,15 +15,26 @@ module TypedDag::Sql::SelectClosure
|
|
15
15
|
def sql
|
16
16
|
<<-SQL
|
17
17
|
SELECT
|
18
|
-
|
19
|
-
|
20
|
-
#{
|
18
|
+
#{from_column},
|
19
|
+
#{to_column},
|
20
|
+
#{type_columns.join(', ')},
|
21
|
+
SUM(#{count_column}) AS #{count_column}
|
21
22
|
FROM
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
(SELECT
|
24
|
+
r1.#{from_column},
|
25
|
+
r2.#{to_column},
|
26
|
+
#{depth_sum_case},
|
27
|
+
r1.#{count_column} * r2.#{count_column} AS #{count_column}
|
28
|
+
FROM
|
29
|
+
#{table_name} r1
|
30
|
+
JOIN
|
31
|
+
#{table_name} r2
|
32
|
+
ON
|
33
|
+
(#{relations_join_combines_paths_condition})) unique_rows
|
34
|
+
GROUP BY
|
35
|
+
#{from_column},
|
36
|
+
#{to_column},
|
37
|
+
#{type_columns.join(', ')}
|
27
38
|
SQL
|
28
39
|
end
|
29
40
|
|
@@ -40,7 +51,7 @@ module TypedDag::Sql::SelectClosure
|
|
40
51
|
ELSE 0
|
41
52
|
END AS #{column}
|
42
53
|
SQL
|
43
|
-
end.join(', ')
|
54
|
+
end.map(&:strip).join(', ')
|
44
55
|
end
|
45
56
|
|
46
57
|
def relations_join_combines_paths_condition
|
@@ -13,7 +13,7 @@ module TypedDag::Sql::TruncateClosure
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def sql
|
16
|
-
if mysql_db?
|
16
|
+
if helper.mysql_db?
|
17
17
|
sql_mysql
|
18
18
|
else
|
19
19
|
sql_postgresql
|
@@ -26,39 +26,37 @@ module TypedDag::Sql::TruncateClosure
|
|
26
26
|
|
27
27
|
def sql_mysql
|
28
28
|
<<-SQL
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
UPDATE #{table_name}
|
30
|
+
JOIN
|
31
|
+
(#{closure_select}) removed_#{table_name}
|
32
|
+
ON #{table_name}.#{from_column} = removed_#{table_name}.#{from_column}
|
33
|
+
AND #{table_name}.#{to_column} = removed_#{table_name}.#{to_column}
|
34
|
+
AND #{types_equality_condition}
|
35
|
+
SET
|
36
|
+
#{table_name}.#{count_column} = #{table_name}.#{count_column} - removed_#{table_name}.#{count_column}
|
36
37
|
SQL
|
37
38
|
end
|
38
39
|
|
39
40
|
def sql_postgresql
|
40
41
|
<<-SQL
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
UPDATE #{table_name}
|
43
|
+
SET
|
44
|
+
#{count_column} = #{table_name}.#{count_column} - removed_#{table_name}.#{count_column}
|
45
|
+
FROM
|
46
|
+
(#{closure_select}) removed_#{table_name}
|
47
|
+
WHERE #{table_name}.#{from_column} = removed_#{table_name}.#{from_column}
|
48
|
+
AND #{table_name}.#{to_column} = removed_#{table_name}.#{to_column}
|
49
|
+
AND #{types_equality_condition}
|
46
50
|
SQL
|
47
51
|
end
|
48
52
|
|
49
53
|
def selection_table
|
50
54
|
<<-SQL
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
GROUP BY #{from_column}, #{to_column}, #{type_select_list}) criteria
|
57
|
-
|
58
|
-
JOIN
|
59
|
-
(#{rank_similar_relations}) ranked
|
60
|
-
ON
|
61
|
-
#{ranked_critieria_join_condition})
|
55
|
+
(
|
56
|
+
SELECT COUNT(*) #{count_column}, #{from_column}, #{to_column}, #{type_select_list}
|
57
|
+
FROM
|
58
|
+
(#{closure_select}) aggregation
|
59
|
+
GROUP BY #{from_column}, #{to_column}, #{type_select_list})
|
62
60
|
SQL
|
63
61
|
end
|
64
62
|
|
@@ -66,113 +64,10 @@ module TypedDag::Sql::TruncateClosure
|
|
66
64
|
TypedDag::Sql::SelectClosure.sql(relation)
|
67
65
|
end
|
68
66
|
|
69
|
-
def rank_similar_relations
|
70
|
-
if mysql_db?
|
71
|
-
rank_similar_relations_mysql
|
72
|
-
else
|
73
|
-
rank_similar_relations_postgresql
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def ranked_critieria_join_condition
|
78
|
-
<<-SQL
|
79
|
-
ranked.#{from_column} = criteria.#{from_column}
|
80
|
-
AND ranked.#{to_column} = criteria.#{to_column}
|
81
|
-
AND #{types_equality_condition}
|
82
|
-
AND count >= row_number
|
83
|
-
SQL
|
84
|
-
end
|
85
|
-
|
86
|
-
def type_column_values_pairs
|
87
|
-
type_columns.map do |column|
|
88
|
-
[column, relation.send(column)]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
67
|
def types_equality_condition
|
93
68
|
type_columns.map do |column|
|
94
|
-
"
|
69
|
+
"#{table_name}.#{column} = removed_#{table_name}.#{column}"
|
95
70
|
end.join(' AND ')
|
96
71
|
end
|
97
|
-
|
98
|
-
def mysql_db?
|
99
|
-
ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
100
|
-
end
|
101
|
-
|
102
|
-
def rank_similar_relations_mysql
|
103
|
-
<<-SQL
|
104
|
-
SELECT
|
105
|
-
id,
|
106
|
-
#{from_column},
|
107
|
-
#{to_column},
|
108
|
-
#{type_select_list},
|
109
|
-
greatest(@cur_count := IF(#{compare_mysql_variables},
|
110
|
-
@cur_count + 1, 1),
|
111
|
-
least(0, #{assign_mysql_variables})) AS row_number
|
112
|
-
FROM
|
113
|
-
#{table_name}
|
114
|
-
|
115
|
-
CROSS JOIN (SELECT #{initialize_mysql_variables}) params_initialization
|
116
|
-
|
117
|
-
WHERE
|
118
|
-
#{only_relations_in_closure_condition}
|
119
|
-
|
120
|
-
ORDER BY #{from_column}, #{to_column}, #{type_select_list}
|
121
|
-
SQL
|
122
|
-
end
|
123
|
-
|
124
|
-
def rank_similar_relations_postgresql
|
125
|
-
<<-SQL
|
126
|
-
SELECT *, ROW_NUMBER() OVER(
|
127
|
-
PARTITION BY #{from_column}, #{to_column}, #{type_select_list}
|
128
|
-
)
|
129
|
-
FROM
|
130
|
-
#{table_name}
|
131
|
-
WHERE
|
132
|
-
#{only_relations_in_closure_condition}
|
133
|
-
SQL
|
134
|
-
end
|
135
|
-
|
136
|
-
def only_relations_in_closure_condition
|
137
|
-
<<-SQL
|
138
|
-
#{from_column} IN (SELECT #{from_column} FROM #{table_name} WHERE #{to_column} = #{from_id_value}) OR #{from_column} = #{from_id_value}
|
139
|
-
AND
|
140
|
-
#{to_column} IN (SELECT #{to_column} FROM #{table_name} WHERE #{from_column} = #{from_id_value})
|
141
|
-
SQL
|
142
|
-
end
|
143
|
-
|
144
|
-
def initialize_mysql_variables
|
145
|
-
variable_string = "@cur_count := NULL,
|
146
|
-
@cur_#{from_column} := NULL,
|
147
|
-
@cur_#{to_column} := NULL"
|
148
|
-
|
149
|
-
type_columns.each do |column|
|
150
|
-
variable_string += ", @cur_#{column} := NULL"
|
151
|
-
end
|
152
|
-
|
153
|
-
variable_string
|
154
|
-
end
|
155
|
-
|
156
|
-
def assign_mysql_variables
|
157
|
-
variable_string = "@cur_#{from_column} := #{from_column},
|
158
|
-
@cur_#{to_column} := #{to_column}"
|
159
|
-
|
160
|
-
type_columns.each do |column|
|
161
|
-
variable_string += ", @cur_#{column} := #{column}"
|
162
|
-
end
|
163
|
-
|
164
|
-
variable_string
|
165
|
-
end
|
166
|
-
|
167
|
-
def compare_mysql_variables
|
168
|
-
variable_string = "@cur_#{from_column} = #{from_column} AND
|
169
|
-
@cur_#{to_column} = #{to_column}"
|
170
|
-
|
171
|
-
type_columns.each do |column|
|
172
|
-
variable_string += " AND @cur_#{column} = #{column}"
|
173
|
-
end
|
174
|
-
|
175
|
-
variable_string
|
176
|
-
end
|
177
72
|
end
|
178
73
|
end
|