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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b6b3476419620f407fcfbfedf49a8e14b14673c4
4
- data.tar.gz: 086f8999ac9c845a3c794a225ee170e0af593f26
3
+ metadata.gz: 0b0809f9b876678e23210f46f24f06fd04191a0f
4
+ data.tar.gz: 854147b9660c9f30979224d3d92949ddeb0dcae0
5
5
  SHA512:
6
- metadata.gz: bc22219a6090c31d8c4192de419d49e073cfc470391f0f39c18b223f33cfd788f0d5b332e26c71f5a4d0b117c61e62d29a3741e5414e0e4c56f3d59493eef75b
7
- data.tar.gz: 87838a9c5454a50140d8221099a9f0dce25ae9ab808a1dd7f7d1304e0b1f8b336aa48db83ea12a83d78fa4dc99b9dc20d84013da017d80153a2ca847c15588cc
6
+ metadata.gz: a8a7c3422d3592263efdb14a76063a6bfd78fb573cf6bc904b1a8053c0baa5272924576a61b87319d28ef8b44c9de6562ef98c669f16733c6e9c4fa1a5246787
7
+ data.tar.gz: 4b5c5dec025229542565c9ba2f5de62b983cf442bb040d547012e7c0a55e52f3c0093a6c720b3aa78d0437dc8ce67e965ead58f6b5cf903868ad034a07233136
data/README.rdoc CHANGED
@@ -16,7 +16,7 @@ A short rundown of some of the major features:
16
16
  It creates a separated class to hold each enum set that can be used by multiple
17
17
  models, it also keeps the database consistent. The enum type is known to have
18
18
  better performance against string- and integer-like enums.
19
- {PostgreSQL Docs}[https://www.postgresql.org/docs/9.2/static/datatype-enum.html]
19
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/static/datatype-enum.html]
20
20
 
21
21
  create_enum :roles, %i(visitor manager admin)
22
22
 
@@ -28,11 +28,84 @@ better performance against string- and integer-like enums.
28
28
 
29
29
  {Learn more}[link:classes/Torque/PostgreSQL/Attributes/Enum.html]
30
30
 
31
+ * Enum set type manager
32
+
33
+ The enum type is known to have a better performance against string- and integer-
34
+ like enums. Now with the array option, which behaves like binary assignment,
35
+ each record can have multiple enum values.
36
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/static/datatype-enum.html]
37
+
38
+ create_enum :permissions, %i(read write exec)
39
+
40
+ add_column :posts, :creator_permissions, :permissions, array: true
41
+
42
+ Enum::PermissionsSet.new(3) # [:read, :write]
43
+
44
+ post.creator_permissions.write?
45
+
46
+ {Learn more}[link:classes/Torque/PostgreSQL/Attributes/EnumSet.html]
47
+
48
+ * Period complex queries
49
+
50
+ This provides extended and complex calculations over date and time ranges. In a
51
+ few words, you can now store `start_time` and `finish_time` in the same column
52
+ and relies on the methods provided here to fo your magic.
53
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/functions-range.html]
54
+
55
+ add_column :events, :period, :tsrange
56
+ add_column :events, :interval, :interval
57
+
58
+ Event.create(title: 'Test', period: ['2019-01-01 12:00:00', '2019-01-01 14:00:00'], interval: 15.minutes)
59
+
60
+ Event.overlapping('2019-01-01 13:00:00', '2019-01-01 15:00:00').count
61
+
62
+ Event.not_real_overlapping('2019-01-01 11:00:00', '2019-01-01 13:00:00').empty?
63
+
64
+ {Learn more}[link:classes/Torque/PostgreSQL/Attributes/Builder/Period.html]
65
+
66
+ * Has many array association
67
+
68
+ The idea is simple, one table stores all the ids and the other one says that
69
+ `has many` records on that table because its records ids exist in the column of
70
+ the array. Like: `Tag has many Videos connected through an array`.
71
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/arrays.html]
72
+
73
+ add_column :videos, :tag_ids, :bigint, array: true
74
+
75
+ Tag.has_many :videos, array: true
76
+
77
+ Tag.videos.size
78
+
79
+ Tag.videos << another_video
80
+
81
+ {Learn more}[link:classes/Torque/PostgreSQL/Reflection/AbstractReflection.html]
82
+
83
+ * Belongs to many association
84
+
85
+ The original `belongs_to` associations define a `SingularAssociation`, which
86
+ means that it could be extended with `array: true`. In this case, I decided to
87
+ create my own `CollectionAssociation` called `belongs_to_many`, which behaves
88
+ similar to the single one, but storing and returning a list of records.
89
+
90
+ With this, now you can say things like `Project belongs to many employees`,
91
+ which is more syntactically correct than `Project has many employees`
92
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/arrays.html]
93
+
94
+ add_column :videos, :tag_ids, :bigint, array: true
95
+
96
+ Video.belongs_to_many :tags
97
+
98
+ Video.tags.size
99
+
100
+ Video.tags << Tag.new(title: 'rails')
101
+
102
+ {Learn more}[link:classes/Torque/PostgreSQL/Reflection/BelongsToManyReflection.html]
103
+
31
104
  * Distinct On
32
105
 
33
106
  MySQL-like group by statement on queries. It keeps only the first row of each
34
107
  set of rows where the given expressions evaluate to equal.
35
- {PostgreSQL Docs}[https://www.postgresql.org/docs/9.5/static/sql-select.html#SQL-DISTINCT]
108
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/static/sql-select.html#SQL-DISTINCT]
36
109
 
37
110
  User.distinct_on(:name).all
38
111
 
@@ -42,7 +115,7 @@ set of rows where the given expressions evaluate to equal.
42
115
 
43
116
  Provides a way to write auxiliary statements for use in a larger query. It's
44
117
  reconfigured on the model, and then can be used during querying process.
45
- {PostgreSQL Docs}[https://www.postgresql.org/docs/9.1/static/queries-with.html]
118
+ {PostgreSQL Docs}[https://www.postgresql.org/docs/9.6/static/queries-with.html]
46
119
 
47
120
  class User < ActiveRecord::Base
48
121
  auxiliary_statement :last_comment do |cte|
@@ -1 +1,2 @@
1
1
  require 'torque/postgresql'
2
+ require 'torque/range'
@@ -4,23 +4,29 @@ require 'active_model'
4
4
  require 'active_record'
5
5
  require 'active_support'
6
6
 
7
+ require 'active_support/core_ext/date/acts_like'
8
+ require 'active_support/core_ext/time/zones'
7
9
  require 'active_support/core_ext/hash/compact'
8
10
  require 'active_record/connection_adapters/postgresql_adapter'
9
11
 
10
12
  require 'torque/postgresql/config'
11
13
  require 'torque/postgresql/version'
12
14
  require 'torque/postgresql/collector'
15
+ require 'torque/postgresql/geometry_builder'
13
16
 
14
17
  require 'torque/postgresql/i18n'
15
18
  require 'torque/postgresql/arel'
16
19
  require 'torque/postgresql/adapter'
20
+ require 'torque/postgresql/associations'
17
21
  require 'torque/postgresql/attributes'
22
+ require 'torque/postgresql/autosave_association'
18
23
  require 'torque/postgresql/auxiliary_statement'
19
24
  require 'torque/postgresql/base'
20
25
  require 'torque/postgresql/inheritance'
21
26
  require 'torque/postgresql/coder'
22
27
  require 'torque/postgresql/migration'
23
28
  require 'torque/postgresql/relation'
29
+ require 'torque/postgresql/reflection'
24
30
  require 'torque/postgresql/schema_cache'
25
31
  require 'torque/postgresql/schema_dumper'
26
32
 
@@ -9,7 +9,6 @@ require_relative 'adapter/schema_statements'
9
9
  module Torque
10
10
  module PostgreSQL
11
11
  module Adapter
12
-
13
12
  include Quoting
14
13
  include ColumnDumper unless Torque::PostgreSQL::AR521
15
14
  include DatabaseStatements
@@ -18,10 +17,9 @@ module Torque
18
17
  # Get the current PostgreSQL version as a Gem Version.
19
18
  def version
20
19
  @version ||= Gem::Version.new(
21
- select_value('SELECT version()')
22
- .match(/#{Adapter::ADAPTER_NAME} ([\d\.]+)/)[1])
20
+ select_value('SELECT version()').match(/#{Adapter::ADAPTER_NAME} ([\d\.]+)/)[1]
21
+ )
23
22
  end
24
-
25
23
  end
26
24
 
27
25
  ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
@@ -3,7 +3,7 @@ module Torque
3
3
  module Adapter
4
4
  module DatabaseStatements
5
5
 
6
- EXTENDED_DATABASE_TYPES = %i(enum interval)
6
+ EXTENDED_DATABASE_TYPES = %i(enum enum_set interval)
7
7
 
8
8
  # Switch between dump mode or not
9
9
  def dump_mode!
@@ -35,7 +35,11 @@ module Torque
35
35
  # Change some of the types being mapped
36
36
  def initialize_type_map(m = type_map)
37
37
  super
38
+ m.register_type 'box', OID::Box.new
39
+ m.register_type 'circle', OID::Circle.new
38
40
  m.register_type 'interval', OID::Interval.new
41
+ m.register_type 'line', OID::Line.new
42
+ m.register_type 'segment', OID::Segment.new
39
43
  end
40
44
 
41
45
  # :nodoc:
@@ -57,7 +61,8 @@ module Torque
57
61
 
58
62
  query = <<-SQL
59
63
  SELECT a.typelem AS oid, t.typname, t.typelem,
60
- t.typdelim, t.typbasetype, t.typtype
64
+ t.typdelim, t.typbasetype, t.typtype,
65
+ t.typarray
61
66
  FROM pg_type t
62
67
  INNER JOIN pg_type a ON (a.oid = t.typarray)
63
68
  LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
@@ -76,14 +81,9 @@ module Torque
76
81
 
77
82
  execute_and_clear(query, 'SCHEMA', []) do |records|
78
83
  records.each do |row|
79
- typtype = row['typtype']
80
- type = begin
81
- case
82
- when typtype == 'e' then OID::Enum.create(row)
83
- end
84
+ case row['typtype']
85
+ when 'e' then OID::Enum.create(row, type_map)
84
86
  end
85
-
86
- type_map.register_type row['oid'].to_i, type
87
87
  end
88
88
  end
89
89
  end
@@ -156,6 +156,20 @@ module Torque
156
156
  SQL
157
157
  end
158
158
 
159
+ # Extracts the value from a PostgreSQL column default definition.
160
+ def extract_value_from_default(default)
161
+ case default
162
+ # Array elements
163
+ when /\AARRAY\[(.*)\]\z/
164
+ # TODO: Improve this since it's not the most safe approach
165
+ eval(default.gsub(/ARRAY|::\w+(\[\])?/, ''))
166
+ else
167
+ super
168
+ end
169
+ rescue SyntaxError
170
+ # If somethin goes wrong with the eval, just return nil
171
+ end
172
+
159
173
  end
160
174
  end
161
175
  end
@@ -1,5 +1,11 @@
1
+ require_relative 'oid/box'
2
+ require_relative 'oid/circle'
1
3
  require_relative 'oid/enum'
4
+ require_relative 'oid/enum_set'
2
5
  require_relative 'oid/interval'
6
+ require_relative 'oid/line'
7
+ require_relative 'oid/range'
8
+ require_relative 'oid/segment'
3
9
 
4
10
  module Torque
5
11
  module PostgreSQL
@@ -7,8 +13,13 @@ module Torque
7
13
  module OID
8
14
  end
9
15
 
10
- ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
16
+ ActiveRecord::Type.register(:box, OID::Box, adapter: :postgresql)
17
+ ActiveRecord::Type.register(:circle, OID::Circle, adapter: :postgresql)
18
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
19
+ ActiveRecord::Type.register(:enum_set, OID::EnumSet, adapter: :postgresql)
11
20
  ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
21
+ ActiveRecord::Type.register(:line, OID::Line, adapter: :postgresql)
22
+ ActiveRecord::Type.register(:segment, OID::Segment, adapter: :postgresql)
12
23
  end
13
24
  end
14
25
  end
@@ -0,0 +1,28 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ class Box < Struct.new(:x1, :y1, :x2, :y2)
4
+ def points
5
+ klass = Torque::PostgreSQL.config.geometry.point_class
6
+ [
7
+ klass.new(x1, y1),
8
+ klass.new(x1, y2),
9
+ klass.new(x2, y1),
10
+ klass.new(x2, y2),
11
+ ]
12
+ end
13
+ end
14
+
15
+ config.geometry.box_class ||= ::ActiveRecord.const_set('Box', Class.new(Box))
16
+
17
+ module Adapter
18
+ module OID
19
+ class Box < Torque::PostgreSQL::GeometryBuilder
20
+
21
+ PIECES = %i[x1 y1 x2 y2].freeze
22
+ FORMATION = '((%s,%s),(%s,%s))'.freeze
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ class Circle < Struct.new(:x, :y, :r)
4
+ alias radius r
5
+ alias radius= r=
6
+
7
+ def center
8
+ point_class.new(x, y)
9
+ end
10
+
11
+ def center=(value)
12
+ parts = value.is_a?(point_class) ? [value.x, value.y] : value[0..1]
13
+ self.x = parts.first
14
+ self.y = parts.last
15
+ end
16
+
17
+ private
18
+
19
+ def point_class
20
+ Torque::PostgreSQL.config.geometry.point_class
21
+ end
22
+ end
23
+
24
+ config.geometry.circle_class ||= ::ActiveRecord.const_set('Circle', Class.new(Circle))
25
+
26
+ module Adapter
27
+ module OID
28
+ class Circle < Torque::PostgreSQL::GeometryBuilder
29
+
30
+ PIECES = %i[x y r].freeze
31
+ FORMATION = '<(%s,%s),%s>'.freeze
32
+
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -6,12 +6,16 @@ module Torque
6
6
 
7
7
  attr_reader :name, :klass
8
8
 
9
- def self.create(row)
10
- new(row['typname'])
11
- end
9
+ def self.create(row, type_map)
10
+ name = row['typname']
11
+ oid = row['oid'].to_i
12
+ arr_oid = row['typarray'].to_i
13
+
14
+ oid_klass = Enum.new(name)
15
+ oid_set_klass = EnumSet.new(name, oid_klass.klass)
12
16
 
13
- def self.auto_initialize?
14
- Torque::PostgreSQL.config.enum.initializer
17
+ type_map.register_type(oid, oid_klass)
18
+ type_map.register_type(arr_oid, oid_set_klass)
15
19
  end
16
20
 
17
21
  def initialize(name)
@@ -0,0 +1,44 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module OID
5
+ class EnumSet < Enum
6
+
7
+ attr_reader :enum_klass
8
+
9
+ def initialize(name, enum_klass)
10
+ @name = name + '[]'
11
+ @klass = Attributes::EnumSet.lookup(name, enum_klass)
12
+ @enum_klass = enum_klass
13
+ end
14
+
15
+ def type
16
+ :enum_set
17
+ end
18
+
19
+ def serialize(value)
20
+ return if value.blank?
21
+ value = cast_value(value)
22
+ value.map(&:to_s) unless value.blank?
23
+ end
24
+
25
+ # Always use symbol values for schema dumper
26
+ def type_cast_for_schema(value)
27
+ cast_value(value).map(&:to_sym).inspect
28
+ end
29
+
30
+ private
31
+
32
+ def cast_value(value)
33
+ return if value.blank?
34
+ return value if value.is_a?(@klass)
35
+ @klass.new(value)
36
+ rescue Attributes::EnumSet::EnumError
37
+ nil
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ class Line < Struct.new(:slope, :intercept)
4
+ alias c intercept
5
+ alias c= intercept=
6
+
7
+ def a=(value)
8
+ self.slope = vertical? \
9
+ ? Float::INFINITY \
10
+ : Rational(value, b)
11
+ end
12
+
13
+ def a
14
+ slope.numerator
15
+ end
16
+
17
+ def b=(value)
18
+ self.slope = value.zero? \
19
+ ? Float::INFINITY \
20
+ : Rational(a, value)
21
+ end
22
+
23
+ def b
24
+ vertical? ? 0 : slope.denominator
25
+ end
26
+
27
+ def horizontal?
28
+ slope.zero?
29
+ end
30
+
31
+ def vertical?
32
+ !slope.try(:infinite?).eql?(nil)
33
+ end
34
+ end
35
+
36
+ config.geometry.line_class ||= ::ActiveRecord.const_set('Line', Class.new(Line))
37
+
38
+ module Adapter
39
+ module OID
40
+ class Line < Torque::PostgreSQL::GeometryBuilder
41
+
42
+ PIECES = %i[a b c].freeze
43
+ FORMATION = '{%s,%s,%s}'.freeze
44
+
45
+ protected
46
+
47
+ def build_klass(*args)
48
+ return nil if args.empty?
49
+ check_invalid_format!(args)
50
+
51
+ a, b, c = args.try(:first, pieces.size)&.map(&:to_f)
52
+ slope = b.zero? ? Float::INFINITY : Rational(a, b)
53
+ config_class.new(slope, c)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end