torque-postgresql 0.2.16 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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