rom-sql 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 83c439ad1fcf4ee22b0a4f8d92f924b41b2a0d19
4
- data.tar.gz: ebd68c2d754a959b7f669cbcc1269d23108a663c
3
+ metadata.gz: dd4d5efa9ebc24506ae85cf772c6d57f6d59530b
4
+ data.tar.gz: 1fe47e3b07ee7979e92d47257b1ecdf4a258246b
5
5
  SHA512:
6
- metadata.gz: 4a8081d7fb278e307d71b3be25fe4d386e2a33aa6794b4706a1d7096daf2a29364a867e52e945a41c388f211ecbc0c280af23fe1a9ab9b9c0e730a0a7e5991cc
7
- data.tar.gz: 8de227092aa395b288d281cbfc69c5370424d76c856ef24f96ef308a4f1f110633556c9021207f74f96b01ee279f0916fc66dcbc20d8d9f4a5d7a99992c5391e
6
+ metadata.gz: 7338e9310e9ea94d333489edde055d213e2b466640c1366702cd08f78d636ee0577b5ec2f16e9e4c47b67847ff50f5fe23476d0440ba4e3fec9ad7c1e265c663
7
+ data.tar.gz: 6c26fb847b68e9f5add7b8255fbf8d4b99ecd387dbcc5897343658c21cf75358b0dc3db369351c70fd592b2f90ce10b428512df0ffeabd747a80685a5b2da97f
@@ -17,14 +17,11 @@ rvm:
17
17
  - 2.3
18
18
  - 2.2
19
19
  - rbx-3
20
- - jruby-9.1.6.0
20
+ - jruby-9.1.7.0
21
21
  env:
22
22
  global:
23
23
  - CODECLIMATE_REPO_TOKEN=03d7f66589572702b12426d2bc71c4de6281a96139e33b335b894264b1f8f0b0
24
24
  - JRUBY_OPTS='--dev -J-Xmx1024M'
25
- matrix:
26
- allow_failures:
27
- - rvm: jruby-9.1.6.0
28
25
  notifications:
29
26
  webhooks:
30
27
  urls:
@@ -1,3 +1,16 @@
1
+ ## v1.0.1 2017-02-09
2
+
3
+ ### Added
4
+
5
+ * Support for inferring the PostgreSQL `hstore` data type (flash-gordon)
6
+ * Support for the rest of geometric PostgreSQL data types (box, lseg, polygon, etc.) (Morozzzko)
7
+ * Added inferring for timestamp types with specified precision (flash-gordon)
8
+ * Added `ROM::SQL::Attribute#in` to support range checks in conditions (flash-gordon)
9
+
10
+ ### Fixed
11
+
12
+ * Missing primary key now leads to a more meaningful error (flash-gordon)
13
+
1
14
  ## v1.0.0 2017-01-29
2
15
 
3
16
  This release is based on rom core 3.0.0 with its improved Schema API, which is extended with SQL-specific features.
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem 'rom-mapper', git: 'https://github.com/rom-rb/rom-mapper.git', branch: 'mast
7
7
 
8
8
  group :test do
9
9
  gem 'byebug', platforms: :mri
10
+ gem 'pry', platforms: %i(jruby rbx)
10
11
  gem 'dry-struct'
11
12
  gem 'activesupport', '~> 5.0'
12
13
  gem 'rspec', '~> 3.1'
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require "bundler/gem_tasks"
1
2
  require "rspec/core/rake_task"
2
3
 
3
4
  RSpec::Core::RakeTask.new(:spec)
@@ -32,10 +32,21 @@ module ROM
32
32
  #
33
33
  # @api private
34
34
  def for_wrap(keys, name)
35
- other = __registry__[name]
36
- other_dataset = other.name.dataset
35
+ rel, other =
36
+ if associations.key?(name)
37
+ assoc = associations[name]
38
+ other = __registry__[assoc.target.to_sym]
37
39
 
38
- schema.merge(other.schema.wrap).qualified.(inner_join(other_dataset, keys))
40
+ [assoc.join(__registry__, :inner_join, self), other]
41
+ else
42
+ # TODO: deprecate this before 2.0
43
+ other = __registry__[name]
44
+ other_dataset = other.name.dataset
45
+
46
+ [qualified.inner_join(other_dataset, keys), other]
47
+ end
48
+
49
+ rel.schema.merge(other.schema.wrap).qualified.(rel)
39
50
  end
40
51
  end
41
52
  end
@@ -149,7 +149,37 @@ module ROM
149
149
  #
150
150
  # @api public
151
151
  def is(other)
152
- Sequel::SQL::BooleanExpression.new(:'=', self, other)
152
+ __cmp__(:'=', other)
153
+ end
154
+
155
+ # Return a boolean expression with an inclusion test
156
+ #
157
+ # If the single argument passed to the method is a Range object
158
+ # then the resulting expression will restrict the attribute value
159
+ # with range's bounds. Upper bound condition will be inclusive/non-inclusive
160
+ # depending on the range type.
161
+ #
162
+ # If more than one argument is passed to the method or the first
163
+ # argument is not Range then the result will be a simple IN check.
164
+ #
165
+ # @example
166
+ # users.where { id.in(1..100) | created_at(((Time.now - 86400)..Time.now)) }
167
+ # users.where { id.in(1, 2, 3) }
168
+ # users.where(users[:id].in(1, 2, 3))
169
+ #
170
+ # @param [Array<Object>] *args A range or a list of values for an inclusion check
171
+ #
172
+ # @api public
173
+ def in(*args)
174
+ if args.first.is_a?(Range)
175
+ range = args.first
176
+ lower_cond = __cmp__(:>=, range.begin)
177
+ upper_cond = __cmp__(range.exclude_end? ? :< : :<=, range.end)
178
+
179
+ Sequel::SQL::BooleanExpression.new(:AND, lower_cond, upper_cond)
180
+ else
181
+ __cmp__(:IN, args)
182
+ end
153
183
  end
154
184
 
155
185
  # Create a function DSL from the attribute
@@ -213,6 +243,14 @@ module ROM
213
243
  super
214
244
  end
215
245
  end
246
+
247
+ # A simple wrapper for the boolean expression constructor where
248
+ # the left part is the attribute value
249
+ #
250
+ # @api private
251
+ def __cmp__(op, r)
252
+ Sequel::SQL::BooleanExpression.new(op, self, r)
253
+ end
216
254
  end
217
255
  end
218
256
  end
@@ -11,6 +11,7 @@ module ROM
11
11
  ForeignKeyConstraintError = Class.new(ConstraintError)
12
12
  CheckConstraintError = Class.new(ConstraintError)
13
13
  UnknownDBTypeError = Class.new(StandardError)
14
+ MissingPrimaryKeyError = Class.new(StandardError)
14
15
 
15
16
  ERROR_MAP = {
16
17
  Sequel::DatabaseError => DatabaseError,
@@ -24,7 +24,14 @@ module ROM
24
24
  'cidr' => Types::PG::IPAddress,
25
25
  'macaddr' => Types::String,
26
26
  'point' => Types::PG::PointT,
27
- 'xml' => Types::String
27
+ 'xml' => Types::String,
28
+ 'hstore' => Types::PG::HStore,
29
+ 'line' => Types::PG::LineT,
30
+ 'circle' => Types::PG::CircleT,
31
+ 'box' => Types::PG::BoxT,
32
+ 'lseg' => Types::PG::LineSegmentT,
33
+ 'polygon' => Types::PG::PolygonT,
34
+ 'path' => Types::PG::PathT
28
35
  ).freeze
29
36
 
30
37
  db_array_type_matcher Sequel::Postgres::PGArray::EMPTY_BRACKET
@@ -52,7 +59,8 @@ module ROM
52
59
  end
53
60
 
54
61
  def map_db_type(db_type)
55
- self.class.db_type_mapping[db_type]
62
+ self.class.db_type_mapping[db_type] ||
63
+ (db_type.start_with?('timestamp') ? Types::Time : nil)
56
64
  end
57
65
 
58
66
  def numeric?(ruby_type, db_type)
@@ -2,7 +2,7 @@ require 'dry-types'
2
2
  require 'sequel'
3
3
  require 'ipaddr'
4
4
 
5
- Sequel.extension(*%i(pg_array pg_array_ops pg_json pg_json_ops))
5
+ Sequel.extension(*%i(pg_array pg_array_ops pg_json pg_json_ops pg_hstore))
6
6
 
7
7
  module ROM
8
8
  module SQL
@@ -14,8 +14,7 @@ module ROM
14
14
 
15
15
  # Array
16
16
 
17
- Array = Dry::Types::Definition
18
- .new(Sequel::Postgres::PGArray)
17
+ Array = Types.Definition(Sequel::Postgres::PGArray)
19
18
 
20
19
  def self.Array(db_type)
21
20
  Array.constructor(-> (v) { Sequel.pg_array(v, db_type) }).meta(type: db_type)
@@ -41,6 +40,11 @@ module ROM
41
40
 
42
41
  JSONB = JSONBArray | JSONBHash | JSONBOp
43
42
 
43
+ # HStore
44
+
45
+ HStoreR = Types.Constructor(Hash, &:to_hash)
46
+ HStore = Types.Constructor(Sequel::Postgres::HStore, &Sequel.method(:hstore)).meta(read: HStoreR)
47
+
44
48
  Bytea = Types.Constructor(Sequel::SQL::Blob, &Sequel::SQL::Blob.method(:new))
45
49
 
46
50
  IPAddressR = Types.Constructor(IPAddr) { |ip| IPAddr.new(ip.to_s) }
@@ -49,14 +53,112 @@ module ROM
49
53
 
50
54
  Money = Types::Decimal
51
55
 
56
+ # Geometric types
57
+
52
58
  Point = ::Struct.new(:x, :y)
53
59
 
60
+ PointD = Types.Definition(Point)
61
+
54
62
  PointTR = Types.Constructor(Point) do |p|
55
63
  x, y = p.to_s[1...-1].split(',', 2)
56
64
  Point.new(Float(x), Float(y))
57
65
  end
58
66
 
59
67
  PointT = Types.Constructor(Point) { |p| "(#{ p.x },#{ p.y })" }.meta(read: PointTR)
68
+
69
+ Line = ::Struct.new(:a, :b, :c)
70
+
71
+ LineTR = Types.Constructor(Line) do |ln|
72
+ a, b, c = ln.to_s[1..-2].split(',', 3)
73
+ Line.new(Float(a), Float(b), Float(c))
74
+ end
75
+
76
+ LineT = Types.Constructor(Line) { |ln| "{#{ ln.a },#{ ln.b },#{ln.c}}"}.meta(read: LineTR)
77
+
78
+ Circle = ::Struct.new(:center, :radius)
79
+
80
+ CircleTR = Types.Constructor(Circle) do |c|
81
+ x, y, r = c.to_s.tr('()<>', '').split(',', 3)
82
+ center = Point.new(Float(x), Float(y))
83
+ Circle.new(center, Float(r))
84
+ end
85
+
86
+ CircleT = Types.Constructor(Circle) { |c| "<(#{ c.center.x },#{ c.center.y }),#{ c.radius }>" }.meta(read: CircleTR)
87
+
88
+ Box = ::Struct.new(:upper_right, :lower_left)
89
+
90
+ BoxTR = Types.Constructor(Box) do |b|
91
+ x_right, y_right, x_left, y_left = b.to_s.tr('()', '').split(',', 4)
92
+ upper_right = Point.new(Float(x_right), Float(y_right))
93
+ lower_left = Point.new(Float(x_left), Float(y_left))
94
+ Box.new(upper_right, lower_left)
95
+ end
96
+
97
+ BoxT = Types.Constructor(Box) { |b| "((#{ b.upper_right.x },#{ b.upper_right.y }),(#{ b.lower_left.x },#{ b.lower_left.y }))" }.meta(read: BoxTR)
98
+
99
+ LineSegment = ::Struct.new(:begin, :end)
100
+
101
+ LineSegmentTR = Types.Constructor(LineSegment) do |lseg|
102
+ x_begin, y_begin, x_end, y_end = lseg.to_s.tr('()[]', '').split(',', 4)
103
+ point_begin = Point.new(Float(x_begin), Float(y_begin))
104
+ point_end = Point.new(Float(x_end), Float(y_end))
105
+ LineSegment.new(point_begin, point_end)
106
+ end
107
+
108
+ LineSegmentT = Types.Constructor(LineSegment) do |lseg|
109
+ "[(#{ lseg.begin.x },#{ lseg.begin.y }),(#{ lseg.end.x },#{ lseg.end.y })]"
110
+ end.meta(read: LineSegmentTR)
111
+
112
+ Polygon = Types::Strict::Array.member(PointD)
113
+
114
+ PolygonTR = Polygon.constructor do |p|
115
+ coordinates = p.to_s.tr('()', '').split(',').each_slice(2)
116
+ points = coordinates.map { |x, y| Point.new(Float(x), Float(y)) }
117
+ Polygon[points]
118
+ end
119
+
120
+ PolygonT = PointD.constructor do |path|
121
+ points_joined = path.map { |p| "(#{ p.x },#{ p.y })" }.join(',')
122
+ "(#{ points_joined })"
123
+ end.meta(read: PolygonTR)
124
+
125
+ Path = ::Struct.new(:points, :type) do
126
+ def open?
127
+ type == :open
128
+ end
129
+
130
+ def closed?
131
+ type == :closed
132
+ end
133
+
134
+ def to_a
135
+ points
136
+ end
137
+ end
138
+
139
+ PathD = Types.Definition(Path)
140
+
141
+ PathTR = PathD.constructor do |path|
142
+ open = path.to_s.start_with?('[') && path.to_s.end_with?(']')
143
+ coordinates = path.to_s.tr('()[]', '').split(',').each_slice(2)
144
+ points = coordinates.map { |x, y| Point.new(Float(x), Float(y)) }
145
+
146
+ if open
147
+ Path.new(points, :open)
148
+ else
149
+ Path.new(points, :closed)
150
+ end
151
+ end
152
+
153
+ PathT = PathD.constructor do |path|
154
+ points_joined = path.to_a.map { |p| "(#{ p.x },#{ p.y })" }.join(',')
155
+
156
+ if path.open?
157
+ "[#{ points_joined }]"
158
+ else
159
+ "(#{ points_joined })"
160
+ end
161
+ end.meta(read: PathTR)
60
162
  end
61
163
  end
62
164
  end
@@ -24,7 +24,7 @@ module ROM
24
24
  end
25
25
 
26
26
  CONNECTION_EXTENSIONS = {
27
- postgres: %i(pg_array pg_json pg_enum)
27
+ postgres: %i(pg_array pg_json pg_enum pg_hstore)
28
28
  }.freeze
29
29
 
30
30
  # @!attribute [r] logger
@@ -91,7 +91,12 @@ module ROM
91
91
  #
92
92
  # @api public
93
93
  define_method(:by_pk) do |pk|
94
- where(schema[primary_key] => pk)
94
+ if primary_key.nil?
95
+ raise MissingPrimaryKeyError.new("Missing primary key for "\
96
+ ":#{ schema.name }")
97
+ else
98
+ where(schema[primary_key] => pk)
99
+ end
95
100
  end
96
101
  end
97
102
  end
@@ -9,6 +9,10 @@ module ROM
9
9
  ROM::Types.Constructor(*args, &block)
10
10
  end
11
11
 
12
+ def self.Definition(*args, &block)
13
+ ROM::Types.Definition(*args, &block)
14
+ end
15
+
12
16
  Serial = Int.meta(primary_key: true)
13
17
 
14
18
  Blob = Constructor(Sequel::SQL::Blob, &Sequel::SQL::Blob.method(:new))
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '1.0.0'.freeze
3
+ VERSION = '1.0.1'.freeze
4
4
  end
5
5
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_runtime_dependency 'sequel', '~> 4.42'
21
+ spec.add_runtime_dependency 'sequel', '~> 4.43'
22
22
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
23
23
  spec.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.4'
24
24
  spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
@@ -141,4 +141,112 @@ RSpec.describe 'ROM::SQL::Types' do
141
141
  expect(described_class.meta[:read]['(7.5,30.5)']).to eql(point)
142
142
  end
143
143
  end
144
+
145
+ describe ROM::SQL::Types::PG::HStore do
146
+ let(:mapping) { Hash['hot' => 'cold'] }
147
+ let(:read_type) { described_class.meta[:read] }
148
+
149
+ it 'covertss data to Sequel::Postgres::HStore' do
150
+ expect(described_class[mapping]).to be_a Sequel::Postgres::HStore
151
+ expect(described_class[mapping]).to eql(Sequel.hstore(hot: :cold))
152
+ end
153
+
154
+ it 'reads Sequel::Postgres::HStore as a Hash object' do
155
+ expect(read_type[Sequel.hstore(mapping)]).to eql(mapping)
156
+ expect(read_type[Sequel.hstore(mapping)]).to be_a(Hash)
157
+ end
158
+ end
159
+
160
+ describe ROM::SQL::Types::PG::LineT do
161
+ let(:line) { ROM::SQL::Types::PG::Line.new(2.3, 4.9, 3.1415) }
162
+
163
+ it 'serializes a line using the {A,B,C} format' do
164
+ expect(described_class[line]).to eql('{2.3,4.9,3.1415}')
165
+ end
166
+
167
+ it 'reads the {A,B,C} format' do
168
+ expect(described_class.meta[:read]['{2.3,4.9,3.1415}']).to eql(line)
169
+ end
170
+ end
171
+
172
+ describe ROM::SQL::Types::PG::CircleT do
173
+ let(:center) { ROM::SQL::Types::PG::Point.new(7.5, 30.5) }
174
+ let(:circle) { ROM::SQL::Types::PG::Circle.new(center, 1.2) }
175
+
176
+ it 'serializes a circle using the <(x,y),r> format' do
177
+ expect(described_class[circle]).to eql('<(7.5,30.5),1.2>')
178
+ end
179
+
180
+ it 'reads the <(x,y),r> format' do
181
+ expect(described_class.meta[:read]['<(7.5,30.5),1.2>']).to eql(circle)
182
+ end
183
+ end
184
+
185
+ describe ROM::SQL::Types::PG::BoxT do
186
+ let(:lower_left) { ROM::SQL::Types::PG::Point.new(7.5, 20.5) }
187
+ let(:upper_right) { ROM::SQL::Types::PG::Point.new(8.5, 30.5) }
188
+
189
+ let(:box) { ROM::SQL::Types::PG::Box.new(upper_right, lower_left) }
190
+
191
+ it 'serializes a box' do
192
+ expect(described_class[box]).to eql('((8.5,30.5),(7.5,20.5))')
193
+ end
194
+
195
+ it 'reads serialized format' do
196
+ expect(described_class.meta[:read]['((8.5,30.5),(7.5,20.5))']).to eql(box)
197
+ end
198
+ end
199
+
200
+ describe ROM::SQL::Types::PG::LineSegmentT do
201
+ let(:first) { ROM::SQL::Types::PG::Point.new(8.5, 30.5) }
202
+ let(:second) { ROM::SQL::Types::PG::Point.new(7.5, 20.5) }
203
+
204
+ let(:lseg) { ROM::SQL::Types::PG::LineSegment.new(first, second) }
205
+
206
+ it 'serializes a lseg using [ ( x1 , y1 ) , ( x2 , y2 ) ] format' do
207
+ expect(described_class[lseg]).to eql('[(8.5,30.5),(7.5,20.5)]')
208
+ end
209
+
210
+ it 'reads serialized format' do
211
+ expect(described_class.meta[:read]['[(8.5,30.5),(7.5,20.5)]']).to eql(lseg)
212
+ end
213
+ end
214
+
215
+ describe ROM::SQL::Types::PG::PolygonT do
216
+ let(:first) { ROM::SQL::Types::PG::Point.new(8.5, 30.5) }
217
+ let(:second) { ROM::SQL::Types::PG::Point.new(7.5, 20.5) }
218
+ let(:third) { ROM::SQL::Types::PG::Point.new(6.5, 10.5) }
219
+
220
+ let(:polygon) { ROM::SQL::Types::PG::Polygon[[first, second, third]] }
221
+
222
+ it 'serializes a polygon using ( ( x1 , y1 ) ... ( xn , yn ) ) format' do
223
+ expect(described_class[polygon]).to eql('((8.5,30.5),(7.5,20.5),(6.5,10.5))')
224
+ end
225
+
226
+ it 'reads serialized format' do
227
+ expect(described_class.meta[:read]['((8.5,30.5),(7.5,20.5),(6.5,10.5))']).to eql(polygon)
228
+ end
229
+ end
230
+
231
+ describe ROM::SQL::Types::PG::PathT do
232
+ let(:first) { ROM::SQL::Types::PG::Point.new(8.5, 30.5) }
233
+ let(:second) { ROM::SQL::Types::PG::Point.new(7.5, 20.5) }
234
+ let(:third) { ROM::SQL::Types::PG::Point.new(6.5, 10.5) }
235
+
236
+ let(:closed_path) { ROM::SQL::Types::PG::Path.new([first, second, third], :closed) }
237
+ let(:open_path) { ROM::SQL::Types::PG::Path.new([first, second, third], :open) }
238
+
239
+ it 'serializes a closed path using ( ( x1 , y1 ) ... ( xn , yn ) ) format' do
240
+ expect(described_class[closed_path]).to eql('((8.5,30.5),(7.5,20.5),(6.5,10.5))')
241
+ end
242
+
243
+ it 'serializes an open path' do
244
+ expect(described_class[open_path]).to eql('[(8.5,30.5),(7.5,20.5),(6.5,10.5)]')
245
+ end
246
+
247
+ it 'reads serialized format' do
248
+ expect(described_class.meta[:read]['((8.5,30.5),(7.5,20.5),(6.5,10.5))']).to eql(closed_path)
249
+ expect(described_class.meta[:read]['[(8.5,30.5),(7.5,20.5),(6.5,10.5)]']).to eql(open_path)
250
+ end
251
+ end
144
252
  end
@@ -201,7 +201,6 @@ RSpec.describe 'Commands / Create', :postgres do
201
201
  end
202
202
 
203
203
  it 're-raises fk constraint violation error' do |ex|
204
- pending 'Waits for https://github.com/jeremyevans/sequel/pull/1283' if jruby? && sqlite?(ex)
205
204
  expect {
206
205
  tasks.try {
207
206
  tasks.create.call(user_id: 918_273_645)
@@ -4,9 +4,13 @@ RSpec.describe 'Plugins / :auto_wrap' do
4
4
 
5
5
  describe '#for_wrap' do
6
6
  shared_context 'joined tuple' do
7
+ let(:name) do
8
+ users.name.relation
9
+ end
10
+
7
11
  it 'returns joined tuples' do
8
12
  task_with_user = tasks
9
- .for_wrap({ id: :user_id }, users.name.relation)
13
+ .for_wrap({ id: :user_id }, name)
10
14
  .where(tasks__id: 2)
11
15
  .one
12
16
 
@@ -51,6 +55,24 @@ RSpec.describe 'Plugins / :auto_wrap' do
51
55
 
52
56
  include_context 'joined tuple'
53
57
  end
58
+
59
+ context 'using association' do
60
+ subject(:tasks) { relations[:tasks] }
61
+
62
+ let(:users) { relations[:users] }
63
+
64
+ before do
65
+ conf.relation(:tasks) {
66
+ schema(infer: true) { associations { belongs_to :users, as: :assignee } }
67
+ }
68
+
69
+ conf.relation(:users) { schema(infer: true) }
70
+ end
71
+
72
+ include_context 'joined tuple' do
73
+ let(:name) { :assignee }
74
+ end
75
+ end
54
76
  end
55
77
  end
56
78
  end
@@ -7,6 +7,11 @@ RSpec.describe 'ROM::SQL::Schema::MysqlInferrer', :mysql do
7
7
  conn.create_table :test_inferrence do
8
8
  tinyint :tiny
9
9
  mediumint :medium
10
+ datetime :created_at
11
+ column :date_and_time, 'datetime(0)'
12
+ column :time_with_ms, 'datetime(3)'
13
+ timestamp :unix_time_usec
14
+ column :unix_time_sec, 'timestamp(0) null'
10
15
  end
11
16
  end
12
17
 
@@ -31,6 +36,11 @@ RSpec.describe 'ROM::SQL::Schema::MysqlInferrer', :mysql do
31
36
  expect(schema.to_h).to eql(
32
37
  tiny: ROM::SQL::Types::Int.optional.meta(name: :tiny, source: source),
33
38
  medium: ROM::SQL::Types::Int.optional.meta(name: :medium, source: source),
39
+ created_at: ROM::SQL::Types::Time.optional.meta(name: :created_at, source: source),
40
+ date_and_time: ROM::SQL::Types::Time.optional.meta(name: :date_and_time, source: source),
41
+ time_with_ms: ROM::SQL::Types::Time.optional.meta(name: :time_with_ms, source: source),
42
+ unix_time_usec: ROM::SQL::Types::Time.meta(name: :unix_time_usec, source: source),
43
+ unix_time_sec: ROM::SQL::Types::Time.optional.meta(name: :unix_time_sec, source: source)
34
44
  )
35
45
  end
36
46
  end
@@ -1,4 +1,3 @@
1
-
2
1
  RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
3
2
  include_context 'database setup'
4
3
 
@@ -7,6 +6,7 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
7
6
  before do
8
7
  conn.extension :pg_enum
9
8
 
9
+ conn.execute('create extension if not exists hstore')
10
10
  conn.drop_table?(:test_inferrence)
11
11
  conn.drop_enum(:rainbow, if_exists: true)
12
12
 
@@ -25,6 +25,16 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
25
25
  rainbow :color
26
26
  point :center
27
27
  xml :page
28
+ hstore :mapping
29
+ line :line
30
+ circle :circle
31
+ box :box
32
+ lseg :lseg
33
+ polygon :polygon
34
+ path :path
35
+ timestamp :created_at
36
+ column :datetime, "timestamp(0) without time zone"
37
+ column :datetime_tz, "timestamp(0) with time zone"
28
38
  end
29
39
  end
30
40
 
@@ -71,7 +81,45 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
71
81
  source: source,
72
82
  read: ROM::SQL::Types::PG::PointTR.optional
73
83
  ),
74
- page: ROM::SQL::Types::String.optional.meta(name: :page, source: source)
84
+ page: ROM::SQL::Types::String.optional.meta(name: :page, source: source),
85
+ mapping: ROM::SQL::Types::PG::HStore.optional.meta(
86
+ name: :mapping,
87
+ source: source,
88
+ read: ROM::SQL::Types::PG::HStoreR.optional
89
+ ),
90
+ line: ROM::SQL::Types::PG::LineT.optional.meta(
91
+ name: :line,
92
+ source: source,
93
+ read: ROM::SQL::Types::PG::LineTR.optional
94
+ ),
95
+ circle: ROM::SQL::Types::PG::CircleT.optional.meta(
96
+ name: :circle,
97
+ source: source,
98
+ read: ROM::SQL::Types::PG::CircleTR.optional
99
+ ),
100
+ box: ROM::SQL::Types::PG::BoxT.optional.meta(
101
+ name: :box,
102
+ source: source,
103
+ read: ROM::SQL::Types::PG::BoxTR.optional
104
+ ),
105
+ lseg: ROM::SQL::Types::PG::LineSegmentT.optional.meta(
106
+ name: :lseg,
107
+ source: source,
108
+ read: ROM::SQL::Types::PG::LineSegmentTR.optional
109
+ ),
110
+ polygon: ROM::SQL::Types::PG::PolygonT.optional.meta(
111
+ name: :polygon,
112
+ source: source,
113
+ read: ROM::SQL::Types::PG::PolygonTR.optional
114
+ ),
115
+ path: ROM::SQL::Types::PG::PathT.optional.meta(
116
+ name: :path,
117
+ source: source,
118
+ read: ROM::SQL::Types::PG::PathTR.optional
119
+ ),
120
+ created_at: ROM::SQL::Types::Time.optional.meta(name: :created_at, source: source),
121
+ datetime: ROM::SQL::Types::Time.optional.meta(name: :datetime, source: source),
122
+ datetime_tz: ROM::SQL::Types::Time.optional.meta(name: :datetime_tz, source: source)
75
123
  )
76
124
  end
77
125
  end
@@ -90,10 +138,20 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
90
138
  context 'with a column with bi-directional mapping' do
91
139
  before do
92
140
  conn.drop_table?(:test_bidirectional)
141
+ conn.execute('create extension if not exists hstore')
142
+
93
143
  conn.create_table(:test_bidirectional) do
94
144
  primary_key :id
95
145
  inet :ip
96
146
  point :center
147
+ hstore :mapping
148
+ line :line
149
+ circle :circle
150
+ box :box
151
+ lseg :lseg
152
+ polygon :polygon
153
+ path :closed_path
154
+ path :open_path
97
155
  end
98
156
 
99
157
  conf.relation(:test_bidirectional) { schema(infer: true) }
@@ -106,14 +164,38 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
106
164
  end
107
165
 
108
166
  let(:point) { ROM::SQL::Types::PG::Point.new(7.5, 30.5) }
167
+ let(:point_2) { ROM::SQL::Types::PG::Point.new(8.5, 35.5) }
168
+ let(:line) { ROM::SQL::Types::PG::Line.new(2.3, 4.9, 3.1415) }
109
169
  let(:dns) { IPAddr.new('8.8.8.8') }
170
+ let(:mapping) { Hash['hot' => 'cold'] }
171
+ let(:circle) { ROM::SQL::Types::PG::Circle.new(point, 1.0) }
172
+ let(:lseg) { ROM::SQL::Types::PG::LineSegment.new(point, point_2) }
173
+ let(:box_corrected) { ROM::SQL::Types::PG::Box.new(point_2, point) }
174
+ let(:box) do
175
+ upper_left = ROM::SQL::Types::PG::Point.new(point.x, point_2.y)
176
+ lower_right = ROM::SQL::Types::PG::Point.new(point_2.x, point.y)
177
+
178
+ ROM::SQL::Types::PG::Box.new(upper_left, lower_right)
179
+ end
180
+ let(:polygon) { ROM::SQL::Types::PG::Polygon[[point, point_2]] }
181
+ let(:closed_path) { ROM::SQL::Types::PG::Path.new([point, point_2], :closed) }
182
+ let(:open_path) { ROM::SQL::Types::PG::Path.new([point, point_2], :open) }
110
183
 
111
184
  let(:relation) { container.relations[:test_bidirectional] }
112
185
  let(:create) { commands[:test_bidirectional].create }
113
186
 
114
- it 'writes and reads data' do
115
- inserted = create.call(id: 1, center: point, ip: dns)
116
- expect(inserted).to eql(id: 1, center: point, ip: dns)
187
+ it 'writes and reads data & corrects data' do
188
+ # Box coordinates are reordered if necessary
189
+ inserted = create.call(
190
+ id: 1, center: point, ip: dns, mapping: mapping,
191
+ line: line, circle: circle, lseg: lseg, box: box,
192
+ polygon: polygon, closed_path: closed_path, open_path: open_path
193
+ )
194
+ expect(inserted).to eql(
195
+ id: 1, center: point, ip: dns, mapping: mapping,
196
+ line: line, circle: circle, lseg: lseg, box: box_corrected,
197
+ polygon: polygon, closed_path: closed_path, open_path: open_path
198
+ )
117
199
  expect(relation.to_a).to eql([inserted])
118
200
  end
119
201
  end
@@ -16,7 +16,8 @@ require 'tempfile'
16
16
 
17
17
  begin
18
18
  require 'byebug'
19
- rescue LoadError # rubocop:disable Lint/HandleExceptions
19
+ rescue LoadError
20
+ require 'pry'
20
21
  end
21
22
 
22
23
  LOGGER = Logger.new(File.open('./log/test.log', 'a'))
@@ -40,11 +41,14 @@ PG_LTE_95 = ENV.fetch('PG_LTE_95', 'true') == 'true'
40
41
 
41
42
  SPEC_ROOT = root = Pathname(__FILE__).dirname
42
43
 
43
- # Redirect inferrer warnings to a log file
44
- $stderr.reopen(SPEC_ROOT.join('../log/warnings.log'), 'w')
45
-
46
44
  TMP_PATH = root.join('../tmp')
47
45
 
46
+ class ROM::SQL::Schema::Inferrer
47
+ def self.on_error(*)
48
+ # quiet in specs
49
+ end
50
+ end
51
+
48
52
  Dir[root.join('shared/**/*')].each { |f| require f }
49
53
  Dir[root.join('support/**/*')].each { |f| require f }
50
54
 
@@ -50,7 +50,7 @@ RSpec.describe ROM::SQL::Gateway, :postgres do
50
50
  extensions = [:pg_array, :pg_array_ops]
51
51
  connection = Sequel.connect uri
52
52
 
53
- expect(connection).to receive(:extension).with(:pg_array, :pg_json, :pg_enum, :pg_array_ops)
53
+ expect(connection).to receive(:extension).with(:pg_array, :pg_json, :pg_enum, :pg_hstore, :pg_array_ops)
54
54
  expect(connection).to receive(:extension).with(:freeze_datasets) unless RUBY_ENGINE == 'rbx'
55
55
 
56
56
  ROM::SQL::Gateway.new(connection, extensions: extensions)
@@ -65,13 +65,17 @@ RSpec.describe 'Plugin / Timestamp' do
65
65
  expect(updated[:updated_at]).not_to eq initial[:updated_at]
66
66
  end
67
67
 
68
- it "allows overriding timestamps" do
68
+ it "allows overriding timestamps" do |ex|
69
69
  tomorrow = (Time.now + (60 * 60 * 24))
70
70
 
71
71
  container.command(:notes).create.call(text: "testing")
72
72
  updated = container.command(:notes).update.call(text: "updated test", updated_at: tomorrow).first
73
73
 
74
- expect(updated[:updated_at].iso8601).to eq tomorrow.iso8601
74
+ if jruby? && sqlite?(ex)
75
+ expect(updated[:updated_at]).to eql(tomorrow.strftime('%Y-%m-%d %H:%M:%S.%6N'))
76
+ else
77
+ expect(updated[:updated_at].iso8601).to eql(tomorrow.iso8601)
78
+ end
75
79
  end
76
80
  end
77
81
  end
@@ -25,5 +25,30 @@ RSpec.describe ROM::Relation, '#by_pk' do
25
25
  expect(relation.by_pk(1, 1).to_a).to eql([{ tag_id: 1, task_id: 1 }])
26
26
  end
27
27
  end
28
+
29
+ context 'without PK' do
30
+ subject(:relation) { relations[:people] }
31
+
32
+ before do
33
+ conn.drop_table?(:people)
34
+
35
+ conn.create_table(:people) do
36
+ column :name, String
37
+ end
38
+
39
+ conf.relation(:people) do
40
+ schema do
41
+ attribute :name, ROM::SQL::Types::String
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'raises a meaningful exception' do
47
+ expect { relation.by_pk(1) }.to \
48
+ raise_error(
49
+ ROM::SQL::MissingPrimaryKeyError,
50
+ 'Missing primary key for :people')
51
+ end
52
+ end
28
53
  end
29
54
  end
@@ -24,5 +24,33 @@ RSpec.describe ROM::Relation, '#where' do
24
24
  it 'restricts relation using canonical attributes' do
25
25
  expect(relation.rename(id: :user_id).where { id > 3 }.to_a).to be_empty
26
26
  end
27
+
28
+ it 'restricts with or condition' do
29
+ expect(relation.where { id.is(1) | id.is(2) }.to_a).
30
+ to eql([{ id: 1, title: "Joe's task" }, { id: 2, title: "Jane's task" }])
31
+ end
32
+
33
+ it 'restricts with a range condition' do
34
+ expect(relation.where { id.in(-1...2) }.to_a).
35
+ to eql([{ id: 1, title: "Joe's task" }])
36
+
37
+ expect(relation.where { id.in(0...3) }.to_a).
38
+ to eql([{ id: 1, title: "Joe's task" }, { id: 2, title: "Jane's task" }])
39
+ end
40
+
41
+ it 'restricts with an inclusive range' do
42
+ expect(relation.where { id.in(0..2) }.to_a).
43
+ to eql([{ id: 1, title: "Joe's task" }, { id: 2, title: "Jane's task" }])
44
+ end
45
+
46
+ it 'restricts with an ordinary enum' do
47
+ expect(relation.where { id.in(2, 3) }.to_a).
48
+ to eql([{ id: 2, title: "Jane's task" }])
49
+ end
50
+
51
+ it 'restricts with enum using self syntax' do
52
+ expect(relation.where(relation[:id].in(2, 3)).to_a).
53
+ to eql([{ id: 2, title: "Jane's task" }])
54
+ end
27
55
  end
28
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-29 00:00:00.000000000 Z
11
+ date: 2017-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.42'
19
+ version: '4.43'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '4.42'
26
+ version: '4.43'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dry-equalizer
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -320,7 +320,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
320
320
  version: '0'
321
321
  requirements: []
322
322
  rubyforge_project:
323
- rubygems_version: 2.5.1
323
+ rubygems_version: 2.5.2
324
324
  signing_key:
325
325
  specification_version: 4
326
326
  summary: SQL databases support for ROM