rom-sql 3.2.0 → 3.4.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 +216 -101
- data/LICENSE +1 -1
- data/README.md +12 -54
- data/lib/rom/plugins/relation/sql/postgres/full_text_search.rb +53 -0
- data/lib/rom/plugins/relation/sql/postgres/streaming.rb +108 -0
- data/lib/rom/sql/associations/many_to_many.rb +1 -1
- data/lib/rom/sql/attribute.rb +17 -2
- data/lib/rom/sql/attribute_aliasing.rb +28 -0
- data/lib/rom/sql/extensions/postgres.rb +1 -0
- data/lib/rom/sql/extensions/postgres/commands.rb +1 -1
- data/lib/rom/sql/function.rb +13 -4
- data/lib/rom/sql/gateway.rb +3 -0
- data/lib/rom/sql/projection_dsl.rb +1 -1
- data/lib/rom/sql/relation.rb +1 -1
- data/lib/rom/sql/relation/reading.rb +13 -1
- data/lib/rom/sql/schema.rb +13 -1
- data/lib/rom/sql/tasks/migration_tasks.rake +1 -1
- data/lib/rom/sql/type_extensions.rb +1 -1
- data/lib/rom/sql/version.rb +1 -1
- metadata +21 -41
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,70 +1,28 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/rom-sql
|
2
2
|
[actions]: https://github.com/rom-rb/rom-sql/actions
|
3
|
-
[
|
4
|
-
[inchpages]: http://inch-ci.org/github/rom-rb/rom-sql
|
3
|
+
[codacy]: https://www.codacy.com/gh/rom-rb/rom-sql
|
5
4
|
[chat]: https://rom-rb.zulipchat.com
|
5
|
+
[inchpages]: http://inch-ci.org/github/rom-rb/rom-sql
|
6
6
|
|
7
|
-
# rom-sql [][chat]
|
8
8
|
|
9
9
|
[][gem]
|
10
10
|
[][actions]
|
11
|
-
[][codacy]
|
12
|
+
[][codacy]
|
13
13
|
[][inchpages]
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
Resources:
|
18
|
-
|
19
|
-
- [User Documentation](http://rom-rb.org/learn/sql/)
|
20
|
-
- [API Documentation](http://rubydoc.info/gems/rom-sql)
|
21
|
-
|
22
|
-
## Installation
|
23
|
-
|
24
|
-
Add this line to your application's Gemfile:
|
25
|
-
|
26
|
-
```ruby
|
27
|
-
gem 'rom-sql'
|
28
|
-
```
|
29
|
-
|
30
|
-
And then execute:
|
31
|
-
|
32
|
-
$ bundle
|
33
|
-
|
34
|
-
Or install it yourself as:
|
35
|
-
|
36
|
-
$ gem install rom-sql
|
37
|
-
|
38
|
-
## Docker
|
39
|
-
|
40
|
-
### Development
|
41
|
-
|
42
|
-
In order to have reproducible environment for development, Docker can be used. Provided it's installed, in order to start developing, one can simply execute:
|
43
|
-
|
44
|
-
```bash
|
45
|
-
docker-compose run --rm gem "bash"
|
46
|
-
```
|
47
|
-
|
48
|
-
If this is the first time this command is executed, it will take some time to set up the dependencies and build the rom-sql container. This should happen only on first execution and in case dependency images are removed.
|
49
|
-
|
50
|
-
After dependencies are set container will be started in a bash shell.
|
51
|
-
|
52
|
-
### Testing
|
53
|
-
|
54
|
-
In order to test the changes, execute:
|
15
|
+
## Links
|
55
16
|
|
56
|
-
|
57
|
-
|
58
|
-
bin/run-specs
|
59
|
-
```
|
17
|
+
* [User documentation](http://rom-rb.org/learn/sql)
|
18
|
+
* [API documentation](http://rubydoc.info/gems/rom-sql)
|
60
19
|
|
61
|
-
|
20
|
+
## Supported Ruby versions
|
62
21
|
|
63
|
-
|
22
|
+
This library officially supports the following Ruby versions:
|
64
23
|
|
65
|
-
|
66
|
-
|
67
|
-
```
|
24
|
+
* MRI >= `2.5`
|
25
|
+
* jruby >= `9.2`
|
68
26
|
|
69
27
|
## License
|
70
28
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Plugins
|
5
|
+
module Relation
|
6
|
+
module SQL
|
7
|
+
module Postgres
|
8
|
+
# PG-specific extensions which adds `Relation#full_text_search` method
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module FullTextSearch
|
12
|
+
# Run a full text search on PostgreSQL.
|
13
|
+
# By default, searching for the inclusion of any of the terms in any of the cols.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# posts.full_text_search([:title, :content], 'apples', language: 'english') # => Relation which match the 'apples' phrase
|
17
|
+
#
|
18
|
+
# @option :headline [String] Append a expression to the selected columns aliased to headline that contains an extract of the matched text.
|
19
|
+
#
|
20
|
+
# @option :language [String] The language to use for the search (default: 'simple')
|
21
|
+
#
|
22
|
+
# @option :plain [Boolean] Whether a plain search should be used (default: false). In this case, terms should be a single string, and it will do a search where cols contains all of the words in terms. This ignores search operators in terms.
|
23
|
+
#
|
24
|
+
# @option :phrase [Boolean] Similar to :plain, but also adding an ILIKE filter to ensure that returned rows also include the exact phrase used.
|
25
|
+
#
|
26
|
+
# @option :rank [Boolean] Set to true to order by the rank, so that closer matches are returned first.
|
27
|
+
#
|
28
|
+
# @option :to_tsquery [Symbol] Can be set to :plain or :phrase to specify the function to use to convert the terms to a ts_query.
|
29
|
+
#
|
30
|
+
# @option :tsquery [Boolean] Specifies the terms argument is already a valid SQL expression returning a tsquery, and can be used directly in the query.
|
31
|
+
#
|
32
|
+
# @option :tsvector [Boolean] Specifies the cols argument is already a valid SQL expression returning a tsvector, and can be used directly in the query.
|
33
|
+
#
|
34
|
+
# @return [Relation]
|
35
|
+
#
|
36
|
+
# @see https://www.postgresql.org/docs/current/textsearch.html PostgreSQL docs
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def full_text_search(*args, &block)
|
40
|
+
new dataset.__send__(__method__, *args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ROM.plugins do
|
50
|
+
adapter :sql do
|
51
|
+
register :pg_full_text_search, ROM::Plugins::Relation::SQL::Postgres::FullTextSearch, type: :relation
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Plugins
|
5
|
+
module Relation
|
6
|
+
module SQL
|
7
|
+
module Postgres
|
8
|
+
# PG-specific extensions which adds `Relation#stream` method
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module Streaming
|
12
|
+
extend Notifications::Listener
|
13
|
+
|
14
|
+
class StreamingNotSupportedError < StandardError; end
|
15
|
+
|
16
|
+
subscribe("configuration.gateway.connected") do |opts|
|
17
|
+
conn = opts[:connection]
|
18
|
+
|
19
|
+
next unless conn.database_type.to_sym == :postgres
|
20
|
+
|
21
|
+
next if defined?(JRUBY_VERSION)
|
22
|
+
|
23
|
+
begin
|
24
|
+
require "sequel_pg"
|
25
|
+
rescue LoadError
|
26
|
+
raise StreamingNotSupportedError, "add sequel_pg to Gemfile to use pg_streaming"
|
27
|
+
end
|
28
|
+
|
29
|
+
unless Sequel::Postgres.supports_streaming?
|
30
|
+
raise StreamingNotSupportedError, "postgres version does not support streaming"
|
31
|
+
end
|
32
|
+
|
33
|
+
conn.extension(:pg_streaming)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(klass)
|
37
|
+
super
|
38
|
+
ROM::Relation::Graph.include(Combined)
|
39
|
+
ROM::Relation::Composite.include(Composite)
|
40
|
+
end
|
41
|
+
|
42
|
+
if defined?(JRUBY_VERSION)
|
43
|
+
# Allows you to stream returned rows one at a time, instead of
|
44
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
45
|
+
#
|
46
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
50
|
+
#
|
51
|
+
# @return [Relation]
|
52
|
+
#
|
53
|
+
# @api publicY_VERSION
|
54
|
+
def stream_each
|
55
|
+
raise StreamingNotSupportedError, "not supported on jruby"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# Allows you to stream returned rows one at a time, instead of
|
59
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
60
|
+
#
|
61
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
65
|
+
#
|
66
|
+
# @return [Relation]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def stream_each
|
70
|
+
return to_enum unless block_given?
|
71
|
+
|
72
|
+
ds = dataset.stream
|
73
|
+
|
74
|
+
if auto_map?
|
75
|
+
ds.each { |tuple| yield(mapper.([output_schema[tuple]]).first) }
|
76
|
+
else
|
77
|
+
ds.each { |tuple| yield(output_schema[tuple]) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module Combined
|
82
|
+
def stream_each
|
83
|
+
raise StreamingNotSupportedError, "not supported on combined relations"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module Composite
|
88
|
+
def stream_each
|
89
|
+
return to_enum unless block_given?
|
90
|
+
|
91
|
+
left.stream_each do |tuple|
|
92
|
+
yield right.call([tuple]).first
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
ROM.plugins do
|
105
|
+
adapter :sql do
|
106
|
+
register :pg_streaming, ROM::Plugins::Relation::SQL::Postgres::Streaming, type: :relation
|
107
|
+
end
|
108
|
+
end
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -67,6 +67,21 @@ module ROM
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
# Return a new attribute that is aliased and marked as qualified
|
71
|
+
#
|
72
|
+
# Intended to be used when passing attributes to `dataset#select`
|
73
|
+
#
|
74
|
+
# @return [SQL::Attribute]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def qualified_projection(table_alias = nil)
|
78
|
+
if aliased?
|
79
|
+
qualified(table_alias).aliased(self.alias)
|
80
|
+
else
|
81
|
+
qualified(table_alias)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
70
85
|
# Return a new attribute marked as joined
|
71
86
|
#
|
72
87
|
# Whenever you join two schemas, the right schema's attribute
|
@@ -285,11 +300,11 @@ module ROM
|
|
285
300
|
# @api private
|
286
301
|
def to_sql_name
|
287
302
|
@_to_sql_name ||=
|
288
|
-
if qualified? &&
|
303
|
+
if qualified? && aliased_projection?
|
289
304
|
Sequel.qualify(table_name, name).as(self.alias)
|
290
305
|
elsif qualified?
|
291
306
|
Sequel.qualify(table_name, name)
|
292
|
-
elsif
|
307
|
+
elsif aliased_projection?
|
293
308
|
Sequel.as(name, self.alias)
|
294
309
|
else
|
295
310
|
Sequel[name]
|
@@ -21,6 +21,34 @@ module ROM
|
|
21
21
|
end
|
22
22
|
alias as aliased
|
23
23
|
|
24
|
+
|
25
|
+
# Return true if this attribute is an aliased projection
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# class Tasks < ROM::Relation[:memory]
|
29
|
+
# schema do
|
30
|
+
# attribute :user_id, Types::Integer, alias: :id
|
31
|
+
# attribute :name, Types::String
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Users.schema[:user_id].aliased?
|
36
|
+
# # => true
|
37
|
+
# Users.schema[:user_id].aliased_projection?
|
38
|
+
# # => false
|
39
|
+
#
|
40
|
+
# Users.schema[:user_id].qualified_projection.aliased?
|
41
|
+
# # => true
|
42
|
+
# Users.schema[:user_id].qualified_projection.aliased_projection?
|
43
|
+
# # => true
|
44
|
+
#
|
45
|
+
# @return [TrueClass,FalseClass]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
def aliased_projection?
|
49
|
+
self.meta[:sql_expr].is_a?(Sequel::SQL::AliasedExpression)
|
50
|
+
end
|
51
|
+
|
24
52
|
private
|
25
53
|
|
26
54
|
# @api private
|
data/lib/rom/sql/function.rb
CHANGED
@@ -78,6 +78,15 @@ module ROM
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
81
|
+
# @see Attribute#qualified_projection
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def qualified_projection(table_alias = nil)
|
85
|
+
meta(
|
86
|
+
func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified_projection) ? arg.qualified_projection(table_alias) : arg })
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
81
90
|
# @see Attribute#qualified?
|
82
91
|
#
|
83
92
|
# @api private
|
@@ -183,10 +192,10 @@ module ROM
|
|
183
192
|
# Filter aggregate using the specified conditions
|
184
193
|
#
|
185
194
|
# @example
|
186
|
-
# users.project { integer::count(:id).filter(name.is("Jack")).as(:jacks) }.
|
187
|
-
# users.project { integer::count(:id).filter { name.is("John") }).as(:johns) }.
|
195
|
+
# users.project { integer::count(:id).filter(name.is("Jack")).as(:jacks) }.unordered
|
196
|
+
# users.project { integer::count(:id).filter { name.is("John") }).as(:johns) }.ordered
|
188
197
|
#
|
189
|
-
# @param [Hash,SQL::Attribute] Conditions
|
198
|
+
# @param condition [Hash,SQL::Attribute] Conditions
|
190
199
|
# @yield [block] A block with restrictions
|
191
200
|
#
|
192
201
|
# @return [SQL::Function]
|
@@ -211,7 +220,7 @@ module ROM
|
|
211
220
|
# @example
|
212
221
|
# households.project { fload::percentile_cont(0.5).within_group(income).as(:percentile) }
|
213
222
|
#
|
214
|
-
# @param [Array] A list of expressions for sorting within a group
|
223
|
+
# @param args [Array] A list of expressions for sorting within a group
|
215
224
|
# @yield [block] A block for getting the expressions using the Order DSL
|
216
225
|
#
|
217
226
|
# @return [SQL::Function]
|
data/lib/rom/sql/gateway.rb
CHANGED
@@ -81,6 +81,7 @@ module ROM
|
|
81
81
|
def initialize(uri, options = EMPTY_HASH)
|
82
82
|
@connection = connect(uri, options)
|
83
83
|
load_extensions(Array(options[:extensions]))
|
84
|
+
Notifications.trigger("configuration.gateway.connected", connection: @connection)
|
84
85
|
|
85
86
|
@options = options
|
86
87
|
|
@@ -247,4 +248,6 @@ module ROM
|
|
247
248
|
end
|
248
249
|
end
|
249
250
|
end
|
251
|
+
|
252
|
+
Configuration.register_event("configuration.gateway.connected")
|
250
253
|
end
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -37,7 +37,7 @@ module ROM
|
|
37
37
|
table = opts[:from].first
|
38
38
|
|
39
39
|
if db.table_exists?(table)
|
40
|
-
select(*schema.
|
40
|
+
select(*schema.qualified_projection).order(*schema.project(*schema.primary_key_names).qualified)
|
41
41
|
else
|
42
42
|
self
|
43
43
|
end
|
@@ -481,6 +481,18 @@ module ROM
|
|
481
481
|
end
|
482
482
|
end
|
483
483
|
|
484
|
+
# Removes ordering for the relation
|
485
|
+
#
|
486
|
+
# @example
|
487
|
+
# users.unordered
|
488
|
+
#
|
489
|
+
# @return [Relation]
|
490
|
+
#
|
491
|
+
# @api public
|
492
|
+
def unordered
|
493
|
+
new(dataset.unordered)
|
494
|
+
end
|
495
|
+
|
484
496
|
# Reverse the order of the relation
|
485
497
|
#
|
486
498
|
# @example
|
@@ -1094,7 +1106,7 @@ module ROM
|
|
1094
1106
|
join_opts = EMPTY_HASH
|
1095
1107
|
end
|
1096
1108
|
|
1097
|
-
new(dataset.__send__(type, other.name.to_sym, join_cond, join_opts))
|
1109
|
+
new(dataset.__send__(type, other.name.dataset.to_sym, join_cond, join_opts))
|
1098
1110
|
else
|
1099
1111
|
associations[other.name.key].join(type, self, other)
|
1100
1112
|
end
|