activerecord-hierarchical_query 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -30
  3. data/lib/active_record/hierarchical_query.rb +11 -11
  4. data/lib/active_record/hierarchical_query/cte/columns.rb +2 -15
  5. data/lib/active_record/hierarchical_query/cte/cycle_detector.rb +54 -0
  6. data/lib/active_record/hierarchical_query/cte/non_recursive_term.rb +32 -11
  7. data/lib/active_record/hierarchical_query/cte/query_builder.rb +82 -0
  8. data/lib/active_record/hierarchical_query/cte/recursive_term.rb +27 -13
  9. data/lib/active_record/hierarchical_query/cte/union_term.rb +10 -6
  10. data/lib/active_record/hierarchical_query/join_builder.rb +31 -10
  11. data/lib/active_record/hierarchical_query/orderings.rb +113 -0
  12. data/lib/active_record/hierarchical_query/{builder.rb → query.rb} +66 -36
  13. data/lib/active_record/hierarchical_query/version.rb +1 -1
  14. data/lib/arel/nodes/postgresql.rb +26 -6
  15. data/spec/active_record/hierarchical_query_spec.rb +56 -32
  16. data/spec/database.yml +1 -9
  17. data/spec/schema.rb +2 -2
  18. data/spec/spec_helper.rb +2 -4
  19. data/spec/support/models.rb +4 -4
  20. metadata +28 -33
  21. data/lib/active_record/hierarchical_query/adapters.rb +0 -34
  22. data/lib/active_record/hierarchical_query/adapters/abstract.rb +0 -41
  23. data/lib/active_record/hierarchical_query/adapters/postgresql.rb +0 -19
  24. data/lib/active_record/hierarchical_query/cte/query.rb +0 -65
  25. data/lib/active_record/hierarchical_query/visitors/orderings.rb +0 -87
  26. data/lib/active_record/hierarchical_query/visitors/postgresql/cycle_detector.rb +0 -49
  27. data/lib/active_record/hierarchical_query/visitors/postgresql/orderings.rb +0 -70
  28. data/lib/active_record/hierarchical_query/visitors/visitor.rb +0 -17
@@ -1,34 +0,0 @@
1
- # coding: utf-8
2
-
3
- require 'active_support/core_ext/hash/keys'
4
- require 'active_support/core_ext/string/inflections'
5
-
6
- module ActiveRecord
7
- module HierarchicalQuery
8
- module Adapters
9
- SUPPORTED_ADAPTERS = %w(PostgreSQL)
10
-
11
- ADAPTERS = Hash[
12
- :PostgreSQL => :PostgreSQL,
13
- :PostGIS => :PostgreSQL,
14
- :OracleEnhanced => :Oracle
15
- ].stringify_keys
16
-
17
- def self.autoload(name, path = name.to_s.underscore)
18
- super name, "active_record/hierarchical_query/adapters/#{path}"
19
- end
20
-
21
- autoload :PostgreSQL, 'postgresql'
22
- autoload :Oracle
23
-
24
- def self.lookup(klass)
25
- name = klass.connection.adapter_name
26
-
27
- raise 'Your database %s does not support recursive queries' % name unless
28
- ADAPTERS.key?(name)
29
-
30
- const_get(ADAPTERS[name])
31
- end
32
- end # module Adapters
33
- end # module HierarchicalQuery
34
- end # module ActiveRecord
@@ -1,41 +0,0 @@
1
- # coding: utf-8
2
-
3
- require 'active_record/hierarchical_query/visitors/visitor'
4
-
5
- module ActiveRecord
6
- module HierarchicalQuery
7
- module Adapters
8
- # @api private
9
- class Abstract
10
- attr_reader :query,
11
- :table
12
-
13
- delegate :klass, :to => :query
14
-
15
- class << self
16
- def visitors(*visitors_classes)
17
- define_method :visitors do
18
- @visitors ||= visitors_classes.map { |klass| klass.new(@query) }
19
- end
20
- end
21
- end
22
-
23
- # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
24
- def initialize(query)
25
- @query = query
26
- @table = klass.arel_table
27
- end
28
-
29
- # @example
30
- # visit(:recursive_term, recursive_term.arel)
31
- def visit(kind, object)
32
- method_name = "visit_#{kind}"
33
-
34
- visitors.reduce(object) do |*, visitor|
35
- visitor.send(method_name, object) if visitor.respond_to?(method_name)
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,19 +0,0 @@
1
- # coding: utf-8
2
-
3
- require 'arel/nodes/postgresql'
4
-
5
- require 'active_record/hierarchical_query/adapters/abstract'
6
- require 'active_record/hierarchical_query/visitors/postgresql/cycle_detector'
7
- require 'active_record/hierarchical_query/visitors/postgresql/orderings'
8
-
9
- module ActiveRecord
10
- module HierarchicalQuery
11
- module Adapters
12
- # @api private
13
- class PostgreSQL < Abstract
14
- visitors Visitors::PostgreSQL::CycleDetector,
15
- Visitors::PostgreSQL::Orderings
16
- end # class PostgreSQL
17
- end # module Adapters
18
- end # module HierarchicalQuery
19
- end # module ActiveRecord
@@ -1,65 +0,0 @@
1
- # coding: utf-8
2
-
3
- require 'active_record/hierarchical_query/adapters'
4
- require 'active_record/hierarchical_query/cte/columns'
5
- require 'active_record/hierarchical_query/cte/union_term'
6
-
7
- module ActiveRecord
8
- module HierarchicalQuery
9
- module CTE
10
- # CTE query builder
11
- class Query
12
- attr_reader :builder,
13
- :adapter,
14
- :columns
15
-
16
- delegate :klass, :table, :to => :builder
17
-
18
- # @param [ActiveRecord::HierarchicalQuery::Builder] builder
19
- def initialize(builder)
20
- @builder = builder
21
- @adapter = Adapters.lookup(klass).new(self)
22
- @columns = Columns.new(self)
23
- end
24
-
25
- # @return [Arel::SelectManager]
26
- def arel
27
- adapter.visit(:cte, build_arel)
28
- end
29
-
30
- # @return [Arel::Table]
31
- def recursive_table
32
- @recursive_table ||= Arel::Table.new("#{table.name}__recursive")
33
- end
34
-
35
- def join_conditions
36
- builder.connect_by_value[recursive_table, table]
37
- end
38
-
39
- private
40
- # "categories__recursive" AS (
41
- # SELECT ... FROM "categories"
42
- # UNION ALL
43
- # SELECT ... FROM "categories"
44
- # INNER JOIN "categories__recursive" ON ...
45
- # )
46
- def with_query
47
- Arel::Nodes::As.new(recursive_table, union_term.arel)
48
- end
49
-
50
- def union_term
51
- UnionTerm.new(self)
52
- end
53
-
54
- def build_arel
55
- Arel::SelectManager.new(table.engine).
56
- with(:recursive, with_query).
57
- from(recursive_table).
58
- project(recursive_table[Arel.star]).
59
- take(builder.limit_value).
60
- skip(builder.offset_value)
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,87 +0,0 @@
1
- module ActiveRecord
2
- module HierarchicalQuery
3
- module Visitors
4
- class Orderings < Visitor
5
- ORDERING_COLUMN_ALIAS = '__order_column'
6
-
7
- delegate :recursive_table, :to => :query
8
-
9
- def visit_joined_relation(relation)
10
- visit(relation) do
11
- relation.order(order_clause)
12
- end
13
- end
14
-
15
- def visit_cte(select_manager)
16
- if should_order_cte?
17
- select_manager.order(order_clause)
18
- else
19
- select_manager
20
- end
21
- end
22
-
23
- protected
24
- def visit(object)
25
- if orderings.any?
26
- yield
27
- else
28
- object
29
- end
30
- end
31
-
32
- def order_clause
33
- recursive_table[column_name].asc
34
- end
35
-
36
- def should_order_cte?
37
- orderings.any? && builder.limit_value || builder.offset_value
38
- end
39
-
40
- def orderings
41
- @orderings ||= builder.order_values.each_with_object([]) do |value, orderings|
42
- orderings.concat Array.wrap(as_orderings(value))
43
- end
44
- end
45
-
46
- def column_name
47
- ORDERING_COLUMN_ALIAS
48
- end
49
-
50
- def as_orderings(value)
51
- case value
52
- when Arel::Nodes::Ordering
53
- value
54
-
55
- when Arel::Nodes::Node, Arel::Attributes::Attribute
56
- value.asc
57
-
58
- when Symbol
59
- table[value].asc
60
-
61
- when Hash
62
- value.map { |field, dir| table[field].send(dir) }
63
-
64
- when String
65
- value.split(',').map do |expr|
66
- string_as_ordering(expr)
67
- end
68
-
69
- else
70
- raise 'Unknown expression in ORDER BY SIBLINGS clause'
71
- end
72
- end
73
-
74
- def string_as_ordering(expr)
75
- expr.strip!
76
-
77
- if expr.gsub!(/\bdesc\z/i, '')
78
- Arel.sql(expr).desc
79
- else
80
- expr.gsub!(/\basc\z/i, '')
81
- Arel.sql(expr).asc
82
- end
83
- end
84
- end
85
- end
86
- end
87
- end
@@ -1,49 +0,0 @@
1
- module ActiveRecord
2
- module HierarchicalQuery
3
- module Visitors
4
- module PostgreSQL
5
- class CycleDetector < Visitor
6
- COLUMN_NAME = '__path'.freeze
7
-
8
- def visit_non_recursive(arel)
9
- if enabled?
10
- arel.project Arel::Nodes::PostgresArray.new([primary_key]).as(column_name)
11
- end
12
-
13
- arel
14
- end
15
-
16
- def visit_recursive(arel)
17
- if enabled?
18
- arel.project Arel::Nodes::ArrayConcat.new(parent_column, primary_key)
19
- arel.constraints << Arel::Nodes::Not.new(primary_key.eq(any(parent_column)))
20
- end
21
-
22
- arel
23
- end
24
-
25
- private
26
- def enabled?
27
- builder.nocycle_value
28
- end
29
-
30
- def column_name
31
- COLUMN_NAME
32
- end
33
-
34
- def parent_column
35
- query.recursive_table[column_name]
36
- end
37
-
38
- def primary_key
39
- table[klass.primary_key]
40
- end
41
-
42
- def any(argument)
43
- Arel::Nodes::NamedFunction.new('ANY', [argument])
44
- end
45
- end
46
- end
47
- end
48
- end
49
- end
@@ -1,70 +0,0 @@
1
- require 'active_record/hierarchical_query/visitors/orderings'
2
-
3
- module ActiveRecord
4
- module HierarchicalQuery
5
- module Visitors
6
- module PostgreSQL
7
- class Orderings < Visitors::Orderings
8
- NATURAL_SORT_TYPES = Set[
9
- :integer, :float, :decimal,
10
- :datetime, :timestamp, :time, :date,
11
- :boolean, :itet, :cidr, :ltree
12
- ]
13
-
14
- delegate :first, :to => :orderings
15
-
16
- def visit_non_recursive(arel)
17
- project(arel) do
18
- Arel::Nodes::PostgresArray.new([row_number_expression]).as(column_name)
19
- end
20
- end
21
-
22
- def visit_recursive(arel)
23
- project(arel) do
24
- Arel::Nodes::ArrayConcat.new(recursive_table[column_name], row_number_expression)
25
- end
26
- end
27
-
28
- private
29
- def project(arel)
30
- visit(arel) { arel.project(yield) }
31
- end
32
-
33
- def row_number_expression
34
- if raw_ordering?
35
- order_attribute
36
- else
37
- Arel.sql("ROW_NUMBER() OVER (ORDER BY #{orderings.map(&:to_sql).join(', ')})")
38
- end
39
- end
40
-
41
- def raw_ordering?
42
- ordered_by_attribute? &&
43
- (column = order_column) &&
44
- NATURAL_SORT_TYPES.include?(column.type)
45
- end
46
-
47
- def ordered_by_attribute?
48
- orderings.one? && first.ascending? && order_attribute.is_a?(Arel::Attributes::Attribute)
49
- end
50
-
51
- def order_attribute
52
- first.expr
53
- end
54
-
55
- def order_column
56
- table = order_attribute.relation
57
-
58
- if table.engine == ActiveRecord::Base
59
- columns = table.engine.connection_pool.columns_hash[table.name]
60
- else
61
- columns = table.engine.columns_hash
62
- end
63
-
64
- columns[order_attribute.name.to_s]
65
- end
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,17 +0,0 @@
1
- module ActiveRecord
2
- module HierarchicalQuery
3
- module Visitors
4
- # @api private
5
- class Visitor
6
- attr_reader :query
7
-
8
- delegate :builder, :to => :query
9
- delegate :klass, :table, :to => :builder
10
-
11
- def initialize(query)
12
- @query = query
13
- end
14
- end
15
- end
16
- end
17
- end