rom-sql 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|