activerecord-hierarchical_query 0.0.2 → 0.0.5

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