torque-postgresql 0.2.16 → 1.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +76 -3
  3. data/lib/torque-postgresql.rb +1 -0
  4. data/lib/torque/postgresql.rb +6 -0
  5. data/lib/torque/postgresql/adapter.rb +2 -4
  6. data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
  7. data/lib/torque/postgresql/adapter/oid.rb +12 -1
  8. data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
  9. data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
  10. data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
  11. data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
  12. data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
  13. data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
  14. data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
  15. data/lib/torque/postgresql/adapter/quoting.rb +21 -0
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
  18. data/lib/torque/postgresql/arel.rb +3 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +32 -0
  21. data/lib/torque/postgresql/arel/operations.rb +18 -0
  22. data/lib/torque/postgresql/arel/visitors.rb +28 -2
  23. data/lib/torque/postgresql/associations.rb +8 -0
  24. data/lib/torque/postgresql/associations/association.rb +30 -0
  25. data/lib/torque/postgresql/associations/association_scope.rb +116 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
  27. data/lib/torque/postgresql/associations/builder.rb +2 -0
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
  30. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +25 -0
  32. data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
  33. data/lib/torque/postgresql/attributes.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  35. data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
  36. data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
  37. data/lib/torque/postgresql/attributes/enum.rb +11 -8
  38. data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
  39. data/lib/torque/postgresql/attributes/lazy.rb +1 -1
  40. data/lib/torque/postgresql/attributes/period.rb +31 -0
  41. data/lib/torque/postgresql/attributes/type_map.rb +3 -5
  42. data/lib/torque/postgresql/autosave_association.rb +40 -0
  43. data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
  44. data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
  45. data/lib/torque/postgresql/base.rb +161 -2
  46. data/lib/torque/postgresql/config.rb +91 -9
  47. data/lib/torque/postgresql/geometry_builder.rb +92 -0
  48. data/lib/torque/postgresql/i18n.rb +1 -1
  49. data/lib/torque/postgresql/railtie.rb +18 -5
  50. data/lib/torque/postgresql/reflection.rb +21 -0
  51. data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
  52. data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
  53. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
  54. data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
  55. data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
  56. data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
  57. data/lib/torque/postgresql/relation.rb +11 -10
  58. data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -2
  60. data/lib/torque/postgresql/relation/merger.rb +11 -7
  61. data/lib/torque/postgresql/schema_cache.rb +1 -1
  62. data/lib/torque/postgresql/version.rb +1 -1
  63. data/lib/torque/range.rb +40 -0
  64. metadata +41 -9
@@ -0,0 +1,52 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module OID
5
+ class Range < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Range
6
+ HASH_PICK = %i[from start end to].freeze
7
+
8
+ module Comparasion
9
+ def <=>(other)
10
+ return super unless other.acts_like?(:date) || other.acts_like?(:time)
11
+ other = other.to_time if other.acts_like?(:date)
12
+ super other.to_i
13
+ end
14
+ end
15
+
16
+ def cast_value(value)
17
+ case value
18
+ when Array
19
+ cast_custom(value[0], value[1])
20
+ when Hash
21
+ pieces = value.with_indifferent_access.values_at(*HASH_PICK)
22
+ cast_custom(pieces[0] || pieces[1], pieces[2] || pieces[3])
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def cast_custom(from, to)
31
+ from = custom_cast_single(from, true)
32
+ to = custom_cast_single(to)
33
+ ::Range.new(from, to)
34
+ end
35
+
36
+ def custom_cast_single(value, negative = false)
37
+ value.blank? ? custom_infinity(negative) : subtype.deserialize(value)
38
+ end
39
+
40
+ def custom_infinity(negative)
41
+ negative ? -::Float::INFINITY : ::Float::INFINITY
42
+ end
43
+ end
44
+
45
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID.send(:remove_const, :Range)
46
+ ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID.const_set(:Range, Range)
47
+
48
+ ::Float.prepend(Range::Comparasion)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,73 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ class Segment < Struct.new(:point0, :point1)
4
+ def x1=(value)
5
+ self.point0 = new_point(value, y1)
6
+ end
7
+
8
+ def x1
9
+ point0.x
10
+ end
11
+
12
+ def y1=(value)
13
+ self.point0 = new_point(x1, value)
14
+ end
15
+
16
+ def y1
17
+ point0.y
18
+ end
19
+
20
+ def x2=(value)
21
+ self.point1 = new_point(value, y2)
22
+ end
23
+
24
+ def x2
25
+ point1.x
26
+ end
27
+
28
+ def y2=(value)
29
+ self.point1 = new_point(x2, value)
30
+ end
31
+
32
+ def y2
33
+ point1.y
34
+ end
35
+
36
+ private
37
+
38
+ def new_point(x, y)
39
+ Torque::PostgreSQL.config.geometry.point_class.new(x, y)
40
+ end
41
+ end
42
+
43
+ config.geometry.segment_class ||= ::ActiveRecord.const_set('Segment', Class.new(Segment))
44
+
45
+ module Adapter
46
+ module OID
47
+ class Segment < Torque::PostgreSQL::GeometryBuilder
48
+
49
+ PIECES = %i[x1 y1 x2 y2].freeze
50
+ FORMATION = '((%s,%s),(%s,%s))'.freeze
51
+
52
+ protected
53
+
54
+ def point_class
55
+ Torque::PostgreSQL.config.geometry.point_class
56
+ end
57
+
58
+ def build_klass(*args)
59
+ return nil if args.empty?
60
+ check_invalid_format!(args)
61
+
62
+ x1, y1, x2, y2 = args.try(:first, pieces.size)&.map(&:to_f)
63
+ config_class.new(
64
+ point_class.new(x1, y1),
65
+ point_class.new(x2, y2),
66
+ )
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -17,6 +17,27 @@ module Torque
17
17
  Name.new(schema, table).quoted
18
18
  end
19
19
 
20
+ def quote_default_expression(value, column)
21
+ if value.is_a?(::Enumerable)
22
+ quote(value) + '::' + column.sql_type
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def _quote(value)
31
+ return super unless value.is_a?(Array)
32
+
33
+ values = value.map(&method(:quote))
34
+ "ARRAY[#{values.join(','.freeze)}]"
35
+ end
36
+
37
+ def _type_cast(value)
38
+ return super unless value.is_a?(Array)
39
+ value.map(&method(:quote)).join(','.freeze)
40
+ end
20
41
  end
21
42
  end
22
43
  end
@@ -20,6 +20,13 @@ module Torque
20
20
  end
21
21
  end
22
22
 
23
+ # Creates a column with an enum array type, needing to specify the
24
+ # subtype, which is basically the name of the type defined prior
25
+ # creating the column
26
+ def enum_set(*args, **options)
27
+ super(*args, **options.merge(array: true))
28
+ end
29
+
23
30
  end
24
31
 
25
32
  module TableDefinition
@@ -10,6 +10,15 @@ module Torque
10
10
  end
11
11
  end
12
12
 
13
+ # Translate +:enum_set+ into +:enum+
14
+ def schema_type(column)
15
+ if column.type == :enum_set
16
+ :enum
17
+ else
18
+ super
19
+ end
20
+ end
21
+
13
22
  # Adds +:subtype+ option to the default set
14
23
  def prepare_column_options(column)
15
24
  spec = super
@@ -24,7 +33,7 @@ module Torque
24
33
  private
25
34
 
26
35
  def schema_subtype(column)
27
- column.sql_type.to_sym.inspect if column.type == :enum
36
+ column.sql_type.to_sym.inspect if column.type == :enum || column.type == :enum_set
28
37
  end
29
38
 
30
39
  end
@@ -1,3 +1,6 @@
1
+ require_relative 'arel/infix_operation'
1
2
  require_relative 'arel/join_source'
3
+ require_relative 'arel/nodes'
4
+ require_relative 'arel/operations'
2
5
  require_relative 'arel/select_manager'
3
6
  require_relative 'arel/visitors'
@@ -0,0 +1,42 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Arel
4
+ nodes = ::Arel::Nodes
5
+ inflix = nodes::InfixOperation
6
+ visitors = ::Arel::Visitors::PostgreSQL
7
+ default_alias = :visit_Arel_Nodes_InfixOperation
8
+
9
+ Math = Module.new
10
+ INFLIX_OPERATION = {
11
+ 'Overlaps' => :'&&',
12
+ 'Contains' => :'@>',
13
+ 'ContainedBy' => :'<@',
14
+ 'HasKey' => :'?',
15
+ 'HasAllKeys' => :'?&',
16
+ 'HasAnyKeys' => :'?|',
17
+ 'StrictlyLeft' => :'<<',
18
+ 'StrictlyRight' => :'>>',
19
+ 'DoesntRightExtend' => :'&<',
20
+ 'DoesntLeftExtend' => :'&>',
21
+ 'AdjacentTo' => :'-|-',
22
+ }.freeze
23
+
24
+ INFLIX_OPERATION.each do |operator_name, operator|
25
+ klass = Class.new(inflix)
26
+ klass.send(:define_method, :initialize) { |*args| super(operator, *args) }
27
+
28
+ nodes.const_set(operator_name, klass)
29
+ visitors.send(:alias_method, :"visit_Arel_Nodes_#{operator_name}", default_alias)
30
+
31
+ # Don't worry about quoting here, if the right side is something that
32
+ # doesn't need quoting, it will leave it as it is
33
+ Math.send(:define_method, operator_name.underscore) do |other|
34
+ klass.new(self, nodes.build_quoted(other, self))
35
+ end
36
+ end
37
+
38
+ ::Arel::Nodes::Node.include(Math)
39
+ ::Arel::Attribute.include(Math)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Arel
4
+ module Nodes
5
+
6
+ class Cast < ::Arel::Nodes::Binary
7
+ include ::Arel::Expressions
8
+ include ::Arel::Predications
9
+ include ::Arel::AliasPredication
10
+ include ::Arel::OrderPredications
11
+ include ::Arel::Math
12
+
13
+ def initialize(left, right, array = false)
14
+ right = right.to_s
15
+ right << '[]' if array
16
+ super left, right
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ ::Arel.define_singleton_method(:array) do |*values, cast: nil|
23
+ values = values.first if values.size.eql?(1) && values.first.is_a?(::Enumerable)
24
+ result = ::Arel::Nodes.build_quoted(values)
25
+ result = result.cast(cast, true) if cast.present?
26
+ result
27
+ end
28
+
29
+ ::Arel::Nodes::Function.include(::Arel::Math)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Arel
4
+ module Operations
5
+
6
+ # Create a cast operation
7
+ def cast(type, array = false)
8
+ Nodes::Cast.new(self, type, array)
9
+ end
10
+
11
+ end
12
+
13
+ ::Arel::Attributes::Attribute.include(Operations)
14
+ ::Arel::Nodes::SqlLiteral.include(Operations)
15
+ ::Arel::Nodes::Node.include(Operations)
16
+ end
17
+ end
18
+ end
@@ -2,7 +2,6 @@ module Torque
2
2
  module PostgreSQL
3
3
  module Arel
4
4
  module Visitors
5
-
6
5
  # Enclose select manager with parenthesis
7
6
  # :TODO: Remove when checking the new version of Arel
8
7
  def visit_Arel_SelectManager o, collector
@@ -16,9 +15,36 @@ module Torque
16
15
  super
17
16
  end
18
17
 
18
+ # Allow quoted arrays to get here
19
+ def visit_Arel_Nodes_Quoted(o, collector)
20
+ return super unless o.expr.is_a?(::Enumerable)
21
+ quote_array(o.expr, collector)
22
+ end
23
+
24
+ # Allow quoted arrays to get here
25
+ def visit_Arel_Nodes_Casted(o, collector)
26
+ return super unless o.val.is_a?(::Enumerable)
27
+ quote_array(o.val, collector)
28
+ end
29
+
30
+ ## TORQUE VISITORS
31
+ # Allow casting any node
32
+ def visit_Torque_PostgreSQL_Arel_Nodes_Cast(o, collector)
33
+ visit(o.left, collector) << '::' << o.right
34
+ end
35
+
36
+ private
37
+
38
+ def quote_array(value, collector)
39
+ value = value.map(&::Arel::Nodes.method(:build_quoted))
40
+
41
+ collector << 'ARRAY['
42
+ visit_Array(value, collector)
43
+ collector << ']'
44
+ end
19
45
  end
20
46
 
21
- ::Arel::Visitors::PostgreSQL.include Visitors
47
+ ::Arel::Visitors::PostgreSQL.prepend(Visitors)
22
48
  end
23
49
  end
24
50
  end
@@ -0,0 +1,8 @@
1
+ require_relative 'associations/association'
2
+ require_relative 'associations/association_scope'
3
+ require_relative 'associations/belongs_to_many_association'
4
+ require_relative 'associations/builder'
5
+ require_relative 'associations/preloader'
6
+
7
+ require_relative 'associations/join_dependency/join_association' \
8
+ unless Torque::PostgreSQL::AR521
@@ -0,0 +1,30 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module Association
5
+
6
+ def inversed_from(record)
7
+ return super unless reflection.connected_through_array?
8
+
9
+ self.target ||= []
10
+ self.target.push(record) unless self.target.include?(record)
11
+ @inversed = self.target.present?
12
+ end
13
+
14
+ private
15
+
16
+ def set_owner_attributes(record)
17
+ return super unless reflection.connected_through_array?
18
+
19
+ add_id = owner[reflection.active_record_primary_key]
20
+ record_fk = reflection.foreign_key
21
+
22
+ record[record_fk].push(add_id) unless (record[record_fk] ||= []).include?(add_id)
23
+ end
24
+
25
+ end
26
+
27
+ ::ActiveRecord::Associations::Association.prepend(Association)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,116 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Associations
4
+ module AssociationScope
5
+
6
+ module ClassMethods
7
+ def get_bind_values(*)
8
+ super.flatten
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ # When the relation is connected through an array, intercept the
15
+ # condition builder and uses an overlap condition building it on
16
+ # +build_id_constraint+
17
+ def last_chain_scope(scope, *args)
18
+ # 5.0 table, reflection, owner, association_klass
19
+ # 5.1 table, reflection, owner
20
+ # 5.2 reflection, owner
21
+
22
+ reflection = args.size.eql?(2) ? args[0] : args[1]
23
+ return super unless reflection.connected_through_array?
24
+
25
+ table = args[0] if args.size > 2
26
+ keys = args.size.eql?(4) ? reflection.join_keys(args[3]) : reflection.join_keys
27
+ owner = args.size.eql?(2) ? args[1] : args[2]
28
+
29
+ value = transform_value(owner[keys.foreign_key])
30
+ constraint, binds = build_id_constraint(reflection, keys, value, table, true)
31
+
32
+ if Torque::PostgreSQL::AR521
33
+ scope.where!(constraint)
34
+ else
35
+ klass = ::ActiveRecord::Relation::WhereClause
36
+ scope.where_clause += klass.new([constraint], binds)
37
+ scope
38
+ end
39
+ end
40
+
41
+ # When the relation is connected through an array, intercept the
42
+ # condition builder and uses an overlap condition building it on
43
+ # +build_id_constraint+
44
+ def next_chain_scope(scope, *args)
45
+ # 5.0 table, reflection, association_klass, foreign_table, next_reflection
46
+ # 5.1 table, reflection, foreign_table, next_reflection
47
+ # 5.2 reflection, next_reflection
48
+
49
+ reflection = args.size.eql?(2) ? args[0] : args[1]
50
+ return super unless reflection.connected_through_array?
51
+
52
+ table = args[0] if args.size > 2
53
+ next_reflection = args[-1]
54
+
55
+ foreign_table = args[-2] if args.size.eql?(5)
56
+ foreign_table ||= next_reflection.aliased_table
57
+
58
+ keys = args.size.eql?(5) ? reflection.join_keys(args[2]) : reflection.join_keys
59
+
60
+ value = foreign_table[keys.foreign_key]
61
+ constraint, *_ = build_id_constraint(reflection, keys, value, table)
62
+
63
+ scope.joins!(join(foreign_table, constraint))
64
+ end
65
+
66
+ # Trigger the same method on the relation which will build the
67
+ # constraint condition using array logics
68
+ def build_id_constraint(reflection, keys, value, table = nil, bind_param = false)
69
+ table ||= reflection.aliased_table
70
+ value, binds = build_binds_for_constraint(reflection, value, keys.foreign_key) \
71
+ if bind_param
72
+
73
+ [reflection.build_id_constraint(table[keys.key], value), binds]
74
+ end
75
+
76
+ # For array-like values, it needs to call the method as many times as
77
+ # the array size
78
+ def transform_value(value)
79
+ if value.is_a?(::Enumerable)
80
+ value.map { |v| value_transformation.call(v) }
81
+ else
82
+ value_transformation.call(value)
83
+ end
84
+ end
85
+
86
+ # When binds are necessary for a constraint, instantiate them
87
+ if Torque::PostgreSQL::AR521
88
+ def build_binds_for_constraint(reflection, values, foreign_key)
89
+ result = Array.wrap(values).map do |value|
90
+ ::Arel::Nodes::BindParam.new(::ActiveRecord::Relation::QueryAttribute.new(
91
+ foreign_key, value, reflection.klass.attribute_types[foreign_key],
92
+ ))
93
+ end
94
+
95
+ [result, nil]
96
+ end
97
+ else
98
+ def build_binds_for_constraint(reflection, values, foreign_key)
99
+ type = reflection.klass.attribute_types[foreign_key]
100
+ parts = Array.wrap(values).map do |value|
101
+ bind = ::Arel::Nodes::BindParam.new
102
+ value = ::ActiveRecord::Relation::QueryAttribute.new(foreign_key, value, type)
103
+ [bind, value]
104
+ end.to_h
105
+
106
+ [parts.keys, parts.values]
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ ::ActiveRecord::Associations::AssociationScope.singleton_class.prepend(AssociationScope::ClassMethods)
113
+ ::ActiveRecord::Associations::AssociationScope.prepend(AssociationScope)
114
+ end
115
+ end
116
+ end