rom-sql 2.0.0 → 2.1.0
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/CHANGELOG.md +14 -0
- data/lib/rom/sql/extensions/postgres/commands.rb +2 -2
- data/lib/rom/sql/extensions/postgres/type_builder.rb +14 -4
- data/lib/rom/sql/extensions/postgres/type_serializer.rb +8 -1
- data/lib/rom/sql/extensions/postgres/types.rb +2 -0
- data/lib/rom/sql/extensions/postgres/types/array.rb +8 -10
- data/lib/rom/sql/extensions/postgres/types/array_types.rb +70 -0
- data/lib/rom/sql/extensions/postgres/types/ltree.rb +329 -0
- data/lib/rom/sql/extensions/postgres/types/range.rb +207 -0
- data/lib/rom/sql/function.rb +31 -0
- data/lib/rom/sql/migration/migrator.rb +1 -1
- data/lib/rom/sql/plugin/associates.rb +2 -2
- data/lib/rom/sql/projection_dsl.rb +1 -1
- data/lib/rom/sql/schema/attributes_inferrer.rb +2 -0
- data/lib/rom/sql/schema/type_builder.rb +2 -2
- data/lib/rom/sql/type_extensions.rb +1 -2
- data/lib/rom/sql/version.rb +1 -1
- data/lib/rom/types/values.rb +22 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0d964bf81ba3e921c992aff816f2c09bc1c91c3a
|
4
|
+
data.tar.gz: 1f8516a4f8420e4a009d41b98d9d1f1dd9bcb608
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 21ac8dc3534190ed1a72d6b0a798cdc60c0ff9207995a304a9edfc44356e41c53a0123fa49718b91b7c093cd53060d1c1adb15a2fd02694e85ceaf310b7647aa
|
7
|
+
data.tar.gz: 63dcf1fcc5c3bc2e5f4dc09335eb2da9138abf83790934daf725651df004c5333e4f15669a7a64c2c78160ffd9326a25fe4feec0d75d1dbbee19b7b41f88ba2d
|
data/CHANGELOG.md
CHANGED
@@ -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
|
@@ -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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
24
|
-
|
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
|
data/lib/rom/sql/function.rb
CHANGED
@@ -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]
|
@@ -121,9 +121,9 @@ module ROM
|
|
121
121
|
|
122
122
|
case parent
|
123
123
|
when Array
|
124
|
-
parent.
|
124
|
+
parent.flat_map do |p|
|
125
125
|
tuples.map { |tuple| Hash(tuple).merge(fk => p[pk]) }
|
126
|
-
end
|
126
|
+
end
|
127
127
|
else
|
128
128
|
tuples.map { |tuple| Hash(tuple).update(fk => parent[pk]) }
|
129
129
|
end
|
@@ -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
|
|
data/lib/rom/sql/version.rb
CHANGED
@@ -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.
|
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-
|
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.
|
243
|
+
rubygems_version: 2.6.13
|
240
244
|
signing_key:
|
241
245
|
specification_version: 4
|
242
246
|
summary: SQL databases support for ROM
|