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 +4 -4
- data/.travis.yml +1 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile +1 -0
- data/Rakefile +1 -0
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +14 -3
- data/lib/rom/sql/attribute.rb +39 -1
- data/lib/rom/sql/errors.rb +1 -0
- data/lib/rom/sql/extensions/postgres/inferrer.rb +10 -2
- data/lib/rom/sql/extensions/postgres/types.rb +105 -3
- data/lib/rom/sql/gateway.rb +1 -1
- data/lib/rom/sql/relation.rb +6 -1
- data/lib/rom/sql/types.rb +4 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +1 -1
- data/spec/extensions/postgres/types_spec.rb +108 -0
- data/spec/integration/commands/create_spec.rb +0 -1
- data/spec/integration/plugins/auto_wrap_spec.rb +23 -1
- data/spec/integration/schema/inferrer/mysql_spec.rb +10 -0
- data/spec/integration/schema/inferrer/postgres_spec.rb +87 -5
- data/spec/spec_helper.rb +8 -4
- data/spec/unit/gateway_spec.rb +1 -1
- data/spec/unit/plugin/timestamp_spec.rb +6 -2
- data/spec/unit/relation/by_pk_spec.rb +25 -0
- data/spec/unit/relation/where_spec.rb +28 -0
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd4d5efa9ebc24506ae85cf772c6d57f6d59530b
|
4
|
+
data.tar.gz: 1fe47e3b07ee7979e92d47257b1ecdf4a258246b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7338e9310e9ea94d333489edde055d213e2b466640c1366702cd08f78d636ee0577b5ec2f16e9e4c47b67847ff50f5fe23476d0440ba4e3fec9ad7c1e265c663
|
7
|
+
data.tar.gz: 6c26fb847b68e9f5add7b8255fbf8d4b99ecd387dbcc5897343658c21cf75358b0dc3db369351c70fd592b2f90ce10b428512df0ffeabd747a80685a5b2da97f
|
data/.travis.yml
CHANGED
@@ -17,14 +17,11 @@ rvm:
|
|
17
17
|
- 2.3
|
18
18
|
- 2.2
|
19
19
|
- rbx-3
|
20
|
-
- jruby-9.1.
|
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:
|
data/CHANGELOG.md
CHANGED
@@ -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
data/Rakefile
CHANGED
@@ -32,10 +32,21 @@ module ROM
|
|
32
32
|
#
|
33
33
|
# @api private
|
34
34
|
def for_wrap(keys, name)
|
35
|
-
other =
|
36
|
-
|
35
|
+
rel, other =
|
36
|
+
if associations.key?(name)
|
37
|
+
assoc = associations[name]
|
38
|
+
other = __registry__[assoc.target.to_sym]
|
37
39
|
|
38
|
-
|
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
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -149,7 +149,37 @@ module ROM
|
|
149
149
|
#
|
150
150
|
# @api public
|
151
151
|
def is(other)
|
152
|
-
|
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
|
data/lib/rom/sql/errors.rb
CHANGED
@@ -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 =
|
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
|
data/lib/rom/sql/gateway.rb
CHANGED
data/lib/rom/sql/relation.rb
CHANGED
@@ -91,7 +91,12 @@ module ROM
|
|
91
91
|
#
|
92
92
|
# @api public
|
93
93
|
define_method(:by_pk) do |pk|
|
94
|
-
|
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
|
data/lib/rom/sql/types.rb
CHANGED
@@ -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))
|
data/lib/rom/sql/version.rb
CHANGED
data/rom-sql.gemspec
CHANGED
@@ -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.
|
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 },
|
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
|
-
|
116
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -16,7 +16,8 @@ require 'tempfile'
|
|
16
16
|
|
17
17
|
begin
|
18
18
|
require 'byebug'
|
19
|
-
rescue LoadError
|
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
|
|
data/spec/unit/gateway_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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.
|
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.
|
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.
|
323
|
+
rubygems_version: 2.5.2
|
324
324
|
signing_key:
|
325
325
|
specification_version: 4
|
326
326
|
summary: SQL databases support for ROM
|