rom-sql 2.0.0 → 2.1.0

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: 33d9d062a6396ba9cf578abc3181d5d7aadfc2b9
4
- data.tar.gz: 5940f64e388cd42a0dbe28a0f6d3517622154fcc
3
+ metadata.gz: 0d964bf81ba3e921c992aff816f2c09bc1c91c3a
4
+ data.tar.gz: 1f8516a4f8420e4a009d41b98d9d1f1dd9bcb608
5
5
  SHA512:
6
- metadata.gz: 22a3d5592b184cb7de84439a7312c0ed97cee07f85e02a876c0c0d6e97758c9f4da65e3ba070183aa2e06640fac4dcf3ea9fdcf5b3989199d37441bbdf04385f
7
- data.tar.gz: d300f4387513f789f3840be6bd561657419ff572de6ceb1decf3a470207448acf36d3d039c2284658c8e11c1445bd0412c6a572e7788f8883d32cb574a868cdd
6
+ metadata.gz: 21ac8dc3534190ed1a72d6b0a798cdc60c0ff9207995a304a9edfc44356e41c53a0123fa49718b91b7c093cd53060d1c1adb15a2fd02694e85ceaf310b7647aa
7
+ data.tar.gz: 63dcf1fcc5c3bc2e5f4dc09335eb2da9138abf83790934daf725651df004c5333e4f15669a7a64c2c78160ffd9326a25fe4feec0d75d1dbbee19b7b41f88ba2d
@@ -1,3 +1,17 @@
1
+ ## v2.1.0 2017-10-23
2
+
3
+ ### Added
4
+
5
+ * Support for PG's range types (v-kolesnikov)
6
+ * Support for PG's `ltree` (GustavoCaso + solnic)
7
+
8
+ ### Fixed
9
+
10
+ * Schema inference works with primary keys that have custom types (ie an enum PK column) (v-kolesnikov)
11
+ * Ruby warnings are gone (solnic)
12
+
13
+ [Compare v2.0.0...v2.1.0](https://github.com/rom-rb/rom-sql/compare/v2.0.0...v2.1.0)
14
+
1
15
  ## v2.0.0 2017-10-18
2
16
 
3
17
  ### Added
@@ -10,9 +10,9 @@ module ROM
10
10
  #
11
11
  # @api private
12
12
  def insert(tuples)
13
- dataset = tuples.map do |tuple|
13
+ dataset = tuples.flat_map do |tuple|
14
14
  relation.dataset.returning.insert(tuple)
15
- end.flatten(1)
15
+ end
16
16
 
17
17
  wrap_dataset(dataset)
18
18
  end
@@ -27,16 +27,23 @@ module ROM
27
27
  'box' => Types::Box,
28
28
  'lseg' => Types::LineSegment,
29
29
  'polygon' => Types::Polygon,
30
- 'path' => Types::Path
30
+ 'path' => Types::Path,
31
+ 'int4range' => Types::Int4Range,
32
+ 'int8range' => Types::Int8Range,
33
+ 'numrange' => Types::NumRange,
34
+ 'tsrange' => Types::TsRange,
35
+ 'tstzrange' => Types::TsTzRange,
36
+ 'daterange' => Types::DateRange,
37
+ 'ltree' => Types::LTree
31
38
  ).freeze
32
39
 
33
40
  db_array_type_matcher '[]'.freeze
34
41
 
35
- def map_pk_type(type, db_type)
42
+ def map_pk_type(type, db_type, options = {})
36
43
  if numeric?(type, db_type)
37
44
  type = self.class.numeric_pk_type
38
45
  else
39
- type = map_type(type, db_type)
46
+ type = map_type(type, db_type, options)
40
47
  end
41
48
 
42
49
  type.meta(primary_key: true)
@@ -44,7 +51,10 @@ module ROM
44
51
 
45
52
  def map_type(ruby_type, db_type, enum_values: nil, **_)
46
53
  if db_type.end_with?(self.class.db_array_type_matcher)
47
- Types::Array(db_type[0...-2])
54
+ member_name = db_type[0...-2]
55
+ member_type = self.class.db_type_mapping[member_name]
56
+
57
+ Types::Array(member_name, member_type)
48
58
  elsif enum_values
49
59
  SQL::Types::String.enum(*enum_values)
50
60
  else
@@ -20,7 +20,14 @@ module ROM
20
20
  Types::Box => 'box',
21
21
  Types::LineSegment => 'lseg',
22
22
  Types::Polygon => 'polygon',
23
- Types::Path => 'path'
23
+ Types::Path => 'path',
24
+ Types::Int4Range => 'int4range',
25
+ Types::Int8Range => 'int8range',
26
+ Types::NumRange => 'numrange',
27
+ Types::TsRange => 'tsrange',
28
+ Types::TsTzRange => 'tstzrange',
29
+ Types::DateRange => 'daterange',
30
+ Types::LTree => 'ltree'
24
31
  )
25
32
  )
26
33
 
@@ -41,3 +41,5 @@ require 'rom/sql/extensions/postgres/types/array'
41
41
  require 'rom/sql/extensions/postgres/types/json'
42
42
  require 'rom/sql/extensions/postgres/types/geometric'
43
43
  require 'rom/sql/extensions/postgres/types/network'
44
+ require 'rom/sql/extensions/postgres/types/range'
45
+ require 'rom/sql/extensions/postgres/types/ltree'
@@ -2,6 +2,8 @@ require 'sequel/core'
2
2
 
3
3
  Sequel.extension(*%i(pg_array pg_array_ops))
4
4
 
5
+ require 'rom/sql/extensions/postgres/types/array_types'
6
+
5
7
  module ROM
6
8
  module SQL
7
9
  module Postgres
@@ -10,18 +12,14 @@ module ROM
10
12
 
11
13
  ArrayRead = Array.constructor { |v| v.respond_to?(:to_ary) ? v.to_ary : v }
12
14
 
13
- constructor = -> type { -> value { Sequel.pg_array(value, type) } }
14
-
15
- @array_types = ::Hash.new do |hash, type|
16
- name = "#{ type }[]"
17
- array_type = Type(name, Array.constructor(constructor.(type))).
18
- meta(type: type, read: ArrayRead)
19
- TypeExtensions.register(array_type) { include ArrayMethods }
20
- hash[type] = array_type
15
+ # @api private
16
+ def self.array_types
17
+ @array_types ||= ArrayTypes.new(Postgres::Types::Array, Postgres::Types::ArrayRead)
21
18
  end
22
19
 
23
- def self.Array(db_type)
24
- @array_types[db_type]
20
+ # @api private
21
+ def self.Array(db_type, member_type = nil)
22
+ array_types[db_type, member_type]
25
23
  end
26
24
 
27
25
  # @!parse
@@ -0,0 +1,70 @@
1
+ require 'rom/sql/type_extensions'
2
+
3
+ module ROM
4
+ module SQL
5
+ module Postgres
6
+ module Types
7
+ # @api private
8
+ class ArrayTypes
9
+ attr_reader :elements
10
+
11
+ attr_reader :constructor
12
+
13
+ attr_reader :base_write_type
14
+
15
+ attr_reader :base_read_type
16
+
17
+ def initialize(base_write_type, base_read_type)
18
+ @elements = {}
19
+ @base_write_type = base_write_type
20
+ @base_read_type = base_read_type
21
+ @constructor = proc { |db_type, member|
22
+ if member
23
+ -> arr { Sequel.pg_array(arr.map { |v| member[v] }, db_type) }
24
+ else
25
+ -> arr { Sequel.pg_array(arr, db_type) }
26
+ end
27
+ }
28
+ end
29
+
30
+ def [](db_type, member_type = nil)
31
+ elements.fetch(db_type) do
32
+ name = "#{db_type}[]"
33
+
34
+ write_type = build_write_type(db_type, member_type)
35
+ read_type = build_read_type(db_type, member_type)
36
+
37
+ array_type = Types.Type(name, write_type).meta(type: db_type, read: read_type)
38
+
39
+ register_extension(array_type)
40
+
41
+ elements[db_type] = array_type
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def build_write_type(db_type, member_type)
48
+ if member_type
49
+ base_write_type.constructor(constructor[db_type, member_type])
50
+ else
51
+ base_write_type.constructor(constructor[db_type])
52
+ end
53
+ end
54
+
55
+ def build_read_type(db_type, member_type)
56
+ if member_type && member_type.meta[:read]
57
+ base_read_type.of(member_type.meta[:read])
58
+ else
59
+ base_read_type
60
+ end
61
+ end
62
+
63
+ def register_extension(type)
64
+ TypeExtensions.register(type) { include ArrayMethods }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,329 @@
1
+ require 'rom/types/values'
2
+
3
+ module ROM
4
+ module SQL
5
+ module Postgres
6
+ # @api public
7
+ module Types
8
+ # @see https://www.postgresql.org/docs/current/static/ltree.html
9
+
10
+ LTree = Type('ltree') do
11
+ SQL::Types.define(ROM::Types::Values::TreePath) do
12
+ input do |label_path|
13
+ label_path.to_s
14
+ end
15
+
16
+ output do |label_path|
17
+ ROM::Types::Values::TreePath.new(label_path.to_s) if label_path
18
+ end
19
+ end
20
+ end
21
+
22
+ # @!parse
23
+ # class SQL::Attribute
24
+ # # @!method match(value)
25
+ # # Check whether the LTree match a lquery value
26
+ # # Translates to the ~ operator
27
+ # #
28
+ # # @example
29
+ # # people.select(:name).where { ltree_tags.match('Bottom.Cities') }
30
+ # #
31
+ # # @param [String] value
32
+ # #
33
+ # # @return [SQL::Attribute<Types::Bool>]
34
+ # #
35
+ # # @api public
36
+ #
37
+ # # @!method match_any(value)
38
+ # # Check whether the LTree match any of the lquery values
39
+ # # Translates to the ? operator
40
+ # #
41
+ # # @example
42
+ # # people.select(:name).where { ltree_tags.match_any(['Bottom', 'Bottom.Cities.*']) }
43
+ # # people.select(:name).where { ltree_tags.match_any('Bottom,Bottom.Cities.*') }
44
+ # #
45
+ # # @param [Array,String] value
46
+ # #
47
+ # # @return [SQL::Attribute<Types::Bool>]
48
+ # #
49
+ # # @api public
50
+ #
51
+ # # @!method match_ltextquery(value)
52
+ # # Check whether the LTree match a ltextquery
53
+ # # Translates to the @ operator
54
+ # #
55
+ # # @example
56
+ # # people.select(:name).where { ltree_tags.match_ltextquery('Countries & Brasil') }
57
+ # #
58
+ # # @param [String] value
59
+ # #
60
+ # # @return [SQL::Attribute<Types::Bool>]
61
+ # #
62
+ # # @api public
63
+ #
64
+ # # @!method contain_descendant(value)
65
+ # # Check whether the LTree is a descendant of the LTree values
66
+ # # Translates to the <@ operator
67
+ # #
68
+ # # @example
69
+ # # people.select(:name).where { ltree_tags.contain_descendant(['Bottom.Cities']) }
70
+ # # people.select(:name).where { ltree_tags.contain_descendant('Bottom.Cities, Bottom.Parks') }
71
+ # #
72
+ # # @param [Array<String>, String] value
73
+ # #
74
+ # # @return [SQL::Attribute<Types::Bool>]
75
+ # #
76
+ # # @api public
77
+ #
78
+ # # @!method descendant(value)
79
+ # # Check whether the LTree is a descendant of the LTree value
80
+ # # Translates to the <@ operator
81
+ # #
82
+ # # @example
83
+ # # people.select(:name).where { ltree_tags.descendant('Bottom.Cities') }
84
+ # #
85
+ # # @param [String] value
86
+ # #
87
+ # # @return [SQL::Attribute<Types::Bool>]
88
+ # #
89
+ # # @api public
90
+ #
91
+ # # @!method contain_ascendant(value)
92
+ # # Check whether the LTree is a ascendant of the LTree values
93
+ # # Translates to the @> operator
94
+ # #
95
+ # # @example
96
+ # # people.select(:name).where { ltree_tags.contain_ascendant(['Bottom.Cities']) }
97
+ # # people.select(:name).where { ltree_tags.contain_ascendant('Bottom.Cities, Bottom.Parks') }
98
+ # #
99
+ # # @param [Array<String>, String] value
100
+ # #
101
+ # # @return [SQL::Attribute<Types::Bool>]
102
+ # #
103
+ # # @api public
104
+ #
105
+ # # @!method ascendant(value)
106
+ # # Check whether the LTree is a ascendant of the LTree value
107
+ # # Translates to the @> operator
108
+ # #
109
+ # # @example
110
+ # # people.select(:name).where { ltree_tags.ascendant('Bottom.Cities') }
111
+ # #
112
+ # # @param [String] value
113
+ # #
114
+ # # @return [SQL::Attribute<Types::Bool>]
115
+ # #
116
+ # # @api public
117
+ #
118
+ # # @!method +(value)
119
+ # # Concatenate two LTree values
120
+ # # Translates to ||
121
+ # #
122
+ # # @example
123
+ # # people.select { (ltree_tags + ROM::Types::Values::TreePath.new('Moscu')).as(:ltree_tags) }.where { name.is('Jade Doe') }
124
+ # # people.select { (ltree_tags + 'Moscu').as(:ltree_tags) }.where { name.is('Jade Doe') }
125
+ # #
126
+ # # @param [LTree, String] keys
127
+ # #
128
+ # # @return [SQL::Attribute<Types::LTree>]
129
+ # #
130
+ # # @api public
131
+ #
132
+ # # @!method contain_any_ltextquery(value)
133
+ # # Does LTree array contain any path matching ltxtquery
134
+ # # Translates to @
135
+ # #
136
+ # # @example
137
+ # # people.select(:name).where { parents_tags.contain_any_ltextquery('Parks')}
138
+ # #
139
+ # # @param [String] value
140
+ # #
141
+ # # @return [SQL::Attribute<Types::Bool>]
142
+ # #
143
+ # # @api public
144
+ #
145
+ # # @!method contain_ancestor(value)
146
+ # # Does LTree array contain an ancestor of ltree
147
+ # # Translates to @>
148
+ # #
149
+ # # @example
150
+ # # people.select(:name).where { parents_tags.contain_ancestor('Top.Building.EmpireState.381')}
151
+ # #
152
+ # # @param [String] value
153
+ # #
154
+ # # @return [SQL::Attribute<Types::PG::Bool>]
155
+ # #
156
+ # # @api public
157
+ #
158
+ # # @!method contain_descendant(value)
159
+ # # Does LTree array contain an descendant of ltree
160
+ # # Translates to <@
161
+ # #
162
+ # # @example
163
+ # # people.select(:name).where { parents_tags.contain_descendant('Top.Building.EmpireState.381')}
164
+ # #
165
+ # # @param [String] value
166
+ # #
167
+ # # @return [SQL::Attribute<Types::PG::Bool>]
168
+ # #
169
+ # # @api public
170
+ #
171
+ # # @!method find_ancestor(value)
172
+ # # Return first LTree array entry that is an ancestor of ltree, NULL if none
173
+ # # Translates to ?@>
174
+ # #
175
+ # # @example
176
+ # # people.select(:name).where { parents_tags.find_ancestor('Left.Parks').not(nil)}
177
+ # #
178
+ # # @param [String] value
179
+ # #
180
+ # # @return [SQL::Attribute<Types::PG::LTree>]
181
+ # #
182
+ # # @api public
183
+ #
184
+ # # @!method find_descendant(value)
185
+ # # Return first LTree array entry that is an descendant of ltree, NULL if none
186
+ # # Translates to ?<@
187
+ # #
188
+ # # @example
189
+ # # people.select(:name).where { parents_tags.find_descendant('Left.Parks').not(nil)}
190
+ # #
191
+ # # @param [String] value
192
+ # #
193
+ # # @return [SQL::Attribute<Types::PG::LTree>]
194
+ # #
195
+ # # @api public
196
+ #
197
+ # # @!method match_any_lquery(value)
198
+ # # Return first LTree array entry that matches lquery, NULL if none
199
+ # # Translates to ?~
200
+ # #
201
+ # # @example
202
+ # # people.select(:name).where { parents_tags.match_any_lquery('Right.*').not(nil)}
203
+ # #
204
+ # # @param [String] value
205
+ # #
206
+ # # @return [SQL::Attribute<Types::PG::LTree>]
207
+ # #
208
+ # # @api public
209
+ #
210
+ # # @!method match_any_ltextquery(value)
211
+ # # Return first LTree array entry that matches ltextquery, NULL if none
212
+ # # Translates to ?@
213
+ # #
214
+ # # @example
215
+ # # people.select(:name).where { parents_tags.match_any_ltextquery('EmpireState').not(nil)}
216
+ # #
217
+ # # @param [String] value
218
+ # #
219
+ # # @return [SQL::Attribute<Types::PG::LTree>]
220
+ # #
221
+ # # @api public
222
+ #
223
+ # end
224
+ module LTreeMethods
225
+ ASCENDANT = ["(".freeze, " @> ".freeze, ")".freeze].freeze
226
+ FIND_ASCENDANT = ["(".freeze, " ?@> ".freeze, ")".freeze].freeze
227
+ DESCENDANT = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
228
+ FIND_DESCENDANT = ["(".freeze, " ?<@ ".freeze, ")".freeze].freeze
229
+ MATCH_ANY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
230
+ MATCH_ANY_LQUERY = ["(".freeze, " ?~ ".freeze, ")".freeze].freeze
231
+ MATCH_LTEXTQUERY = ["(".freeze, " @ ".freeze, ")".freeze].freeze
232
+ MATCH_ANY_LTEXTQUERY = ["(".freeze, " ?@ ".freeze, ")".freeze].freeze
233
+
234
+ def match(type, expr, query)
235
+ Attribute[SQL::Types::Bool].meta(sql_expr: Sequel::SQL::BooleanExpression.new(:'~', expr, query))
236
+ end
237
+
238
+ def match_any(type, expr, query)
239
+ array = build_array_query(query)
240
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(MATCH_ANY, expr, array))
241
+ end
242
+
243
+ private
244
+
245
+ def custom_operator_expr(string, expr, query)
246
+ Sequel::SQL::PlaceholderLiteralString.new(string, [expr, query])
247
+ end
248
+
249
+ def build_array_query(query, array_type = 'lquery')
250
+ case query
251
+ when Array
252
+ ROM::SQL::Types::PG::Array(array_type)[query]
253
+ when String
254
+ ROM::SQL::Types::PG::Array(array_type)[query.split(',')]
255
+ end
256
+ end
257
+ end
258
+
259
+ TypeExtensions.register(ROM::SQL::Types::PG::Array('ltree', LTree)) do
260
+ include LTreeMethods
261
+
262
+ def contain_any_ltextquery(type, expr, query)
263
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::MATCH_LTEXTQUERY, expr, query))
264
+ end
265
+
266
+ def contain_ancestor(type, expr, query)
267
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::ASCENDANT, expr, query))
268
+ end
269
+
270
+ def contain_descendant(type, expr, query)
271
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::DESCENDANT, expr, query))
272
+ end
273
+
274
+ def find_ancestor(type, expr, query)
275
+ Attribute[LTree].meta(sql_expr: custom_operator_expr(LTreeMethods::FIND_ASCENDANT, expr, query))
276
+ end
277
+
278
+ def find_descendant(type, expr, query)
279
+ Attribute[LTree].meta(sql_expr: custom_operator_expr(LTreeMethods::FIND_DESCENDANT, expr, query))
280
+ end
281
+
282
+ def match_any_lquery(type, expr, query)
283
+ Attribute[LTree].meta(sql_expr: custom_operator_expr(LTreeMethods::MATCH_ANY_LQUERY, expr, query))
284
+ end
285
+
286
+ def match_any_ltextquery(type, expr, query)
287
+ Attribute[LTree].meta(sql_expr: custom_operator_expr(LTreeMethods::MATCH_ANY_LTEXTQUERY, expr, query))
288
+ end
289
+ end
290
+
291
+ TypeExtensions.register(LTree) do
292
+ include LTreeMethods
293
+
294
+ def match_ltextquery(type, expr, query)
295
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::MATCH_LTEXTQUERY, expr, query))
296
+ end
297
+
298
+ def contain_descendant(type, expr, query)
299
+ array = build_array_query(query, 'ltree')
300
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::DESCENDANT, expr, array))
301
+ end
302
+
303
+ def descendant(type, expr, query)
304
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::DESCENDANT, expr, query))
305
+ end
306
+
307
+ def contain_ascendant(type, expr, query)
308
+ array = build_array_query(query, 'ltree')
309
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::ASCENDANT, expr, array))
310
+ end
311
+
312
+ def ascendant(type, expr, query)
313
+ Attribute[SQL::Types::Bool].meta(sql_expr: custom_operator_expr(LTreeMethods::ASCENDANT, expr, query))
314
+ end
315
+
316
+ def +(type, expr, other)
317
+ other_value = case other
318
+ when ROM::Types::Values::TreePath
319
+ other
320
+ else
321
+ ROM::Types::Values::TreePath.new(other)
322
+ end
323
+ Attribute[LTree].meta(sql_expr: Sequel::SQL::StringExpression.new(:'||', expr, other_value.to_s))
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,207 @@
1
+ require 'sequel/core'
2
+
3
+ Sequel.extension(:pg_range, :pg_range_ops)
4
+
5
+ module ROM
6
+ module SQL
7
+ module Postgres
8
+ module Values
9
+ Range = ::Struct.new(:lower, :upper, :bounds) do
10
+ PAREN_LEFT = '('.freeze
11
+ PAREN_RIGHT = ')'.freeze
12
+
13
+ def initialize(lower, upper, bounds = :'[)')
14
+ super
15
+ end
16
+
17
+ def exclude_begin?
18
+ bounds[0] == PAREN_LEFT
19
+ end
20
+
21
+ def exclude_end?
22
+ bounds[1] == PAREN_RIGHT
23
+ end
24
+ end
25
+ end
26
+
27
+ # @api public
28
+ module Types
29
+ # The list of range types supported by PostgreSQL
30
+ # @see https://www.postgresql.org/docs/current/static/rangetypes.html
31
+
32
+ @range_parsers = {
33
+ int4range: Sequel::Postgres::PGRange::Parser.new(
34
+ 'int4range', SQL::Types::Coercible::Int
35
+ ),
36
+ int8range: Sequel::Postgres::PGRange::Parser.new(
37
+ 'int8range', SQL::Types::Coercible::Int
38
+ ),
39
+ numrange: Sequel::Postgres::PGRange::Parser.new(
40
+ 'numrange', SQL::Types::Coercible::Int
41
+ ),
42
+ tsrange: Sequel::Postgres::PGRange::Parser.new(
43
+ 'tsrange', SQL::Types::Form::Time
44
+ ),
45
+ tstzrange: Sequel::Postgres::PGRange::Parser.new(
46
+ 'tstzrange', SQL::Types::Form::Time
47
+ ),
48
+ daterange: Sequel::Postgres::PGRange::Parser.new(
49
+ 'daterange', SQL::Types::Form::Date
50
+ )
51
+ }.freeze
52
+
53
+ # @api private
54
+ def self.range_read_type(name)
55
+ SQL::Types.Constructor(Values::Range) do |value|
56
+ pg_range =
57
+ if value.is_a?(Sequel::Postgres::PGRange)
58
+ value
59
+ elsif value && value.respond_to?(:to_s)
60
+ @range_parsers[name].(value.to_s)
61
+ else
62
+ value
63
+ end
64
+
65
+ Values::Range.new(
66
+ pg_range.begin,
67
+ pg_range.end,
68
+ [pg_range.exclude_begin? ? :'(' : :'[',
69
+ pg_range.exclude_end? ? :')' : :']']
70
+ .join('').to_sym
71
+ )
72
+ end
73
+ end
74
+
75
+ # @api private
76
+ def self.range(name, read_type)
77
+ Type(name) do
78
+ type = SQL::Types.Definition(Values::Range).constructor do |range|
79
+ format('%s%s,%s%s',
80
+ range.exclude_begin? ? :'(' : :'[',
81
+ range.lower,
82
+ range.upper,
83
+ range.exclude_end? ? :')' : :']')
84
+ end
85
+
86
+ type.meta(read: read_type)
87
+ end
88
+ end
89
+
90
+ Int4Range = range('int4range', range_read_type(:int4range))
91
+ Int8Range = range('int8range', range_read_type(:int8range))
92
+ NumRange = range('numrange', range_read_type(:numrange))
93
+ TsRange = range('tsrange', range_read_type(:tsrange))
94
+ TsTzRange = range('tstzrange', range_read_type(:tstzrange))
95
+ DateRange = range('daterange', range_read_type(:daterange))
96
+
97
+ module RangeOperators
98
+ def contain(_type, expr, value)
99
+ Attribute[SQL::Types::Bool].meta(
100
+ sql_expr: Sequel.pg_range(expr).contains(value)
101
+ )
102
+ end
103
+
104
+ def contained_by(_type, expr, value)
105
+ Attribute[SQL::Types::Bool].meta(
106
+ sql_expr: Sequel.pg_range(expr).contained_by(value)
107
+ )
108
+ end
109
+
110
+ def overlap(_type, expr, value)
111
+ Attribute[SQL::Types::Bool].meta(
112
+ sql_expr: Sequel.pg_range(expr).overlaps(value)
113
+ )
114
+ end
115
+
116
+ def left_of(_type, expr, value)
117
+ Attribute[SQL::Types::Bool].meta(
118
+ sql_expr: Sequel.pg_range(expr).left_of(value)
119
+ )
120
+ end
121
+
122
+ def right_of(_type, expr, value)
123
+ Attribute[SQL::Types::Bool].meta(
124
+ sql_expr: Sequel.pg_range(expr).right_of(value)
125
+ )
126
+ end
127
+
128
+ def starts_after(_type, expr, value)
129
+ Attribute[SQL::Types::Bool].meta(
130
+ sql_expr: Sequel.pg_range(expr).starts_after(value)
131
+ )
132
+ end
133
+
134
+ def ends_before(_type, expr, value)
135
+ Attribute[SQL::Types::Bool].meta(
136
+ sql_expr: Sequel.pg_range(expr).ends_before(value)
137
+ )
138
+ end
139
+
140
+ def adjacent_to(_type, expr, value)
141
+ Attribute[SQL::Types::Bool].meta(
142
+ sql_expr: Sequel.pg_range(expr).adjacent_to(value)
143
+ )
144
+ end
145
+ end
146
+
147
+ module RangeFunctions
148
+ def lower(_type, expr)
149
+ Attribute[SQL::Types::Bool].meta(
150
+ sql_expr: Sequel.pg_range(expr).lower
151
+ )
152
+ end
153
+
154
+ def upper(_type, expr)
155
+ Attribute[SQL::Types::Bool].meta(
156
+ sql_expr: Sequel.pg_range(expr).upper
157
+ )
158
+ end
159
+
160
+ def is_empty(_type, expr)
161
+ Attribute[SQL::Types::Bool].meta(
162
+ sql_expr: Sequel.pg_range(expr).isempty
163
+ )
164
+ end
165
+
166
+ def lower_inc(_type, expr)
167
+ Attribute[SQL::Types::Bool].meta(
168
+ sql_expr: Sequel.pg_range(expr).lower_inc
169
+ )
170
+ end
171
+
172
+ def upper_inc(_type, expr)
173
+ Attribute[SQL::Types::Bool].meta(
174
+ sql_expr: Sequel.pg_range(expr).upper_inc
175
+ )
176
+ end
177
+
178
+ def lower_inf(_type, expr)
179
+ Attribute[SQL::Types::Bool].meta(
180
+ sql_expr: Sequel.pg_range(expr).lower_inf
181
+ )
182
+ end
183
+
184
+ def upper_inf(_type, expr)
185
+ Attribute[SQL::Types::Bool].meta(
186
+ sql_expr: Sequel.pg_range(expr).upper_inf
187
+ )
188
+ end
189
+ end
190
+
191
+ [
192
+ Int4Range,
193
+ Int8Range,
194
+ NumRange,
195
+ TsRange,
196
+ TsTzRange,
197
+ DateRange
198
+ ].each do |type|
199
+ TypeExtensions.register(type) do
200
+ include RangeOperators
201
+ include RangeFunctions
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -132,8 +132,39 @@ module ROM
132
132
  Attribute[type].meta(sql_expr: ::Sequel.cast(expr, db_type))
133
133
  end
134
134
 
135
+ # Add a FILTER clause to aggregate function (supported by PostgreSQL 9.4+)
136
+ # @see https://www.postgresql.org/docs/current/static/sql-expressions.html
137
+ #
138
+ # Filter aggregate using the specified conditions
139
+ #
140
+ # @example
141
+ # users.project { int::count(:id).filter(name.is("Jack")).as(:jacks) }.order(nil)
142
+ # users.project { int::count(:id).filter { name.is("John") }).as(:johns) }.order(nil)
143
+ #
144
+ # @param [Hash,SQL::Attribute] Conditions
145
+ # @yield [block] A block with restrictions
146
+ #
147
+ # @return [SQL::Function]
148
+ #
149
+ # @api public
150
+ def filter(condition = Undefined, &block)
151
+ if block
152
+ conditions = schema.restriction(&block)
153
+ conditions = conditions & condition unless condition.equal?(Undefined)
154
+ else
155
+ conditions = condition
156
+ end
157
+
158
+ super(conditions)
159
+ end
160
+
135
161
  private
136
162
 
163
+ # @api private
164
+ def schema
165
+ meta[:schema]
166
+ end
167
+
137
168
  # @api private
138
169
  def func
139
170
  meta[:func]
@@ -2,7 +2,7 @@ require 'pathname'
2
2
 
3
3
  require 'rom/types'
4
4
  require 'rom/initializer'
5
- require 'rom/sql/migration'
5
+
6
6
  require 'rom/sql/migration/runner'
7
7
  require 'rom/sql/migration/inline_runner'
8
8
  require 'rom/sql/migration/writer'
@@ -121,9 +121,9 @@ module ROM
121
121
 
122
122
  case parent
123
123
  when Array
124
- parent.map do |p|
124
+ parent.flat_map do |p|
125
125
  tuples.map { |tuple| Hash(tuple).merge(fk => p[pk]) }
126
- end.flatten(1)
126
+ end
127
127
  else
128
128
  tuples.map { |tuple| Hash(tuple).update(fk => parent[pk]) }
129
129
  end
@@ -38,7 +38,7 @@ module ROM
38
38
  type = type(meth)
39
39
 
40
40
  if type
41
- ::ROM::SQL::Function.new(type)
41
+ ::ROM::SQL::Function.new(type, schema: schema)
42
42
  else
43
43
  super
44
44
  end
@@ -33,6 +33,8 @@ module ROM
33
33
  [inferred, missing]
34
34
  end
35
35
 
36
+ undef :with
37
+
36
38
  # @api private
37
39
  def with(new_options)
38
40
  self.class.new(options.merge(new_options))
@@ -37,7 +37,7 @@ module ROM
37
37
 
38
38
  def call(primary_key:, db_type:, type:, allow_null:, **rest)
39
39
  if primary_key
40
- map_pk_type(type, db_type)
40
+ map_pk_type(type, db_type, **rest)
41
41
  else
42
42
  mapped_type = map_type(type, db_type, rest)
43
43
 
@@ -56,7 +56,7 @@ module ROM
56
56
  end
57
57
 
58
58
  # @api private
59
- def map_pk_type(_ruby_type, _db_type)
59
+ def map_pk_type(_ruby_type, _db_type, **)
60
60
  self.class.numeric_pk_type.meta(primary_key: true)
61
61
  end
62
62
 
@@ -34,11 +34,10 @@ module ROM
34
34
  extensions = @types[type.meta[:database]]
35
35
  db_type = type.meta[:db_type]
36
36
 
37
- raise ArgumentError, "Type #{ db_type } already registered" if extensions.key?(db_type)
38
37
  mod = Module.new(&block)
39
38
  ctx = Object.new.extend(mod)
40
39
  functions = mod.public_instance_methods.each_with_object({}) { |m, ms| ms[m] = ctx.method(m) }
41
- extensions[db_type] = functions
40
+ extensions[db_type] = (extensions[db_type] || {}).merge(functions)
42
41
  end
43
42
  end
44
43
 
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '2.0.0'.freeze
3
+ VERSION = '2.1.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,22 @@
1
+ require 'rom/sql/types'
2
+
3
+ module ROM
4
+ module Types
5
+ module Values
6
+ class TreePath < ::Struct.new(:value, :separator)
7
+ DEFAULT_SEPARATOR = '.'.freeze
8
+
9
+ # @api public
10
+ def self.new(value, separator = DEFAULT_SEPARATOR)
11
+ super
12
+ end
13
+
14
+ # @api public
15
+ def to_s
16
+ value
17
+ end
18
+ alias_method :to_str, :to_s
19
+ end
20
+ end
21
+ end
22
+ 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: 2.0.0
4
+ version: 2.1.0
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-10-18 00:00:00.000000000 Z
11
+ date: 2017-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -169,9 +169,12 @@ files:
169
169
  - lib/rom/sql/extensions/postgres/type_serializer.rb
170
170
  - lib/rom/sql/extensions/postgres/types.rb
171
171
  - lib/rom/sql/extensions/postgres/types/array.rb
172
+ - lib/rom/sql/extensions/postgres/types/array_types.rb
172
173
  - lib/rom/sql/extensions/postgres/types/geometric.rb
173
174
  - lib/rom/sql/extensions/postgres/types/json.rb
175
+ - lib/rom/sql/extensions/postgres/types/ltree.rb
174
176
  - lib/rom/sql/extensions/postgres/types/network.rb
177
+ - lib/rom/sql/extensions/postgres/types/range.rb
175
178
  - lib/rom/sql/extensions/rails_log_subscriber.rb
176
179
  - lib/rom/sql/extensions/sqlite.rb
177
180
  - lib/rom/sql/extensions/sqlite/type_builder.rb
@@ -216,6 +219,7 @@ files:
216
219
  - lib/rom/sql/types.rb
217
220
  - lib/rom/sql/version.rb
218
221
  - lib/rom/sql/wrap.rb
222
+ - lib/rom/types/values.rb
219
223
  homepage: http://rom-rb.org
220
224
  licenses:
221
225
  - MIT
@@ -236,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
240
  version: '0'
237
241
  requirements: []
238
242
  rubyforge_project:
239
- rubygems_version: 2.6.11
243
+ rubygems_version: 2.6.13
240
244
  signing_key:
241
245
  specification_version: 4
242
246
  summary: SQL databases support for ROM